Commit 0021f007f1de05bba8e202ebc7c3c3ed0ee4aaa7

Authored by Igor Kulikov
1 parent 7645e6f2

Implement multiple customer assigned dashboards UI. Add Current Customer entity …

…type for dashboard aliases.
Showing 39 changed files with 848 additions and 497 deletions
@@ -434,7 +434,6 @@ public abstract class BaseController { @@ -434,7 +434,6 @@ public abstract class BaseController {
434 try { 434 try {
435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId); 435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId); 436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
437 - SecurityUser authUser = getCurrentUser();  
438 checkDashboard(dashboardInfo); 437 checkDashboard(dashboardInfo);
439 return dashboardInfo; 438 return dashboardInfo;
440 } catch (Exception e) { 439 } catch (Exception e) {
@@ -447,7 +446,7 @@ public abstract class BaseController { @@ -447,7 +446,7 @@ public abstract class BaseController {
447 checkTenantId(dashboard.getTenantId()); 446 checkTenantId(dashboard.getTenantId());
448 SecurityUser authUser = getCurrentUser(); 447 SecurityUser authUser = getCurrentUser();
449 if (authUser.getAuthority() == Authority.CUSTOMER_USER) { 448 if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
450 - if (dashboard.getAssignedCustomers() == null || !dashboard.getAssignedCustomers().containsKey(authUser.getCustomerId().toString())) { 449 + if (!dashboard.isAssignedToCustomer(authUser.getCustomerId())) {
451 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, 450 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
452 ThingsboardErrorCode.PERMISSION_DENIED); 451 ThingsboardErrorCode.PERMISSION_DENIED);
453 } 452 }
@@ -18,10 +18,7 @@ package org.thingsboard.server.controller; @@ -18,10 +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;  
22 -import org.thingsboard.server.common.data.Dashboard;  
23 -import org.thingsboard.server.common.data.DashboardInfo;  
24 -import org.thingsboard.server.common.data.EntityType; 21 +import org.thingsboard.server.common.data.*;
25 import org.thingsboard.server.common.data.audit.ActionType; 22 import org.thingsboard.server.common.data.audit.ActionType;
26 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
27 import org.thingsboard.server.common.data.id.DashboardId; 24 import org.thingsboard.server.common.data.id.DashboardId;
@@ -34,6 +31,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -34,6 +31,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
34 import org.thingsboard.server.dao.model.ModelConstants; 31 import org.thingsboard.server.dao.model.ModelConstants;
35 import org.thingsboard.server.exception.ThingsboardException; 32 import org.thingsboard.server.exception.ThingsboardException;
36 33
  34 +import java.util.HashSet;
  35 +import java.util.Set;
  36 +
37 @RestController 37 @RestController
38 @RequestMapping("/api") 38 @RequestMapping("/api")
39 public class DashboardController extends BaseController { 39 public class DashboardController extends BaseController {
@@ -182,6 +182,158 @@ public class DashboardController extends BaseController { @@ -182,6 +182,158 @@ public class DashboardController extends BaseController {
182 } 182 }
183 183
184 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 184 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  185 + @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
  186 + @ResponseBody
  187 + public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
  188 + @RequestBody String[] strCustomerIds) throws ThingsboardException {
  189 + checkParameter(DASHBOARD_ID, strDashboardId);
  190 + try {
  191 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  192 + Dashboard dashboard = checkDashboardId(dashboardId);
  193 +
  194 + Set<CustomerId> customerIds = new HashSet<>();
  195 + if (strCustomerIds != null) {
  196 + for (String strCustomerId : strCustomerIds) {
  197 + customerIds.add(new CustomerId(toUUID(strCustomerId)));
  198 + }
  199 + }
  200 +
  201 + Set<CustomerId> addedCustomerIds = new HashSet<>();
  202 + Set<CustomerId> removedCustomerIds = new HashSet<>();
  203 + for (CustomerId customerId : customerIds) {
  204 + if (!dashboard.isAssignedToCustomer(customerId)) {
  205 + addedCustomerIds.add(customerId);
  206 + }
  207 + }
  208 +
  209 + Set<ShortCustomerInfo> assignedCustomers = dashboard.getAssignedCustomers();
  210 + if (assignedCustomers != null) {
  211 + for (ShortCustomerInfo customerInfo : assignedCustomers) {
  212 + if (!customerIds.contains(customerInfo.getCustomerId())) {
  213 + removedCustomerIds.add(customerInfo.getCustomerId());
  214 + }
  215 + }
  216 + }
  217 +
  218 + if (addedCustomerIds.isEmpty() && removedCustomerIds.isEmpty()) {
  219 + return dashboard;
  220 + } else {
  221 + Dashboard savedDashboard = null;
  222 + for (CustomerId customerId : addedCustomerIds) {
  223 + savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
  224 + ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
  225 + logEntityAction(dashboardId, savedDashboard,
  226 + customerId,
  227 + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  228 + }
  229 + for (CustomerId customerId : removedCustomerIds) {
  230 + ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
  231 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
  232 + logEntityAction(dashboardId, dashboard,
  233 + customerId,
  234 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  235 +
  236 + }
  237 + return savedDashboard;
  238 + }
  239 + } catch (Exception e) {
  240 +
  241 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  242 + null,
  243 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
  244 +
  245 + throw handleException(e);
  246 + }
  247 + }
  248 +
  249 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  250 + @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
  251 + @ResponseBody
  252 + public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
  253 + @RequestBody String[] strCustomerIds) throws ThingsboardException {
  254 + checkParameter(DASHBOARD_ID, strDashboardId);
  255 + try {
  256 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  257 + Dashboard dashboard = checkDashboardId(dashboardId);
  258 +
  259 + Set<CustomerId> customerIds = new HashSet<>();
  260 + if (strCustomerIds != null) {
  261 + for (String strCustomerId : strCustomerIds) {
  262 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  263 + if (!dashboard.isAssignedToCustomer(customerId)) {
  264 + customerIds.add(customerId);
  265 + }
  266 + }
  267 + }
  268 +
  269 + if (customerIds.isEmpty()) {
  270 + return dashboard;
  271 + } else {
  272 + Dashboard savedDashboard = null;
  273 + for (CustomerId customerId : customerIds) {
  274 + savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
  275 + ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
  276 + logEntityAction(dashboardId, savedDashboard,
  277 + customerId,
  278 + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  279 + }
  280 + return savedDashboard;
  281 + }
  282 + } catch (Exception e) {
  283 +
  284 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  285 + null,
  286 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
  287 +
  288 + throw handleException(e);
  289 + }
  290 + }
  291 +
  292 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  293 + @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
  294 + @ResponseBody
  295 + public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
  296 + @RequestBody String[] strCustomerIds) throws ThingsboardException {
  297 + checkParameter(DASHBOARD_ID, strDashboardId);
  298 + try {
  299 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  300 + Dashboard dashboard = checkDashboardId(dashboardId);
  301 +
  302 + Set<CustomerId> customerIds = new HashSet<>();
  303 + if (strCustomerIds != null) {
  304 + for (String strCustomerId : strCustomerIds) {
  305 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  306 + if (dashboard.isAssignedToCustomer(customerId)) {
  307 + customerIds.add(customerId);
  308 + }
  309 + }
  310 + }
  311 +
  312 + if (customerIds.isEmpty()) {
  313 + return dashboard;
  314 + } else {
  315 + Dashboard savedDashboard = null;
  316 + for (CustomerId customerId : customerIds) {
  317 + ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
  318 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
  319 + logEntityAction(dashboardId, dashboard,
  320 + customerId,
  321 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  322 +
  323 + }
  324 + return savedDashboard;
  325 + }
  326 + } catch (Exception e) {
  327 +
  328 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  329 + null,
  330 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
  331 +
  332 + throw handleException(e);
  333 + }
  334 + }
  335 +
  336 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
185 @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) 337 @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
186 @ResponseBody 338 @ResponseBody
187 public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { 339 public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
@@ -15,12 +15,13 @@ @@ -15,12 +15,13 @@
15 */ 15 */
16 package org.thingsboard.server.service.install; 16 package org.thingsboard.server.service.install;
17 17
  18 +import com.fasterxml.jackson.databind.JavaType;
18 import com.fasterxml.jackson.databind.ObjectMapper; 19 import com.fasterxml.jackson.databind.ObjectMapper;
19 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
20 import org.apache.commons.csv.CSVFormat; 21 import org.apache.commons.csv.CSVFormat;
21 import org.apache.commons.csv.CSVParser; 22 import org.apache.commons.csv.CSVParser;
22 import org.apache.commons.lang3.StringUtils; 23 import org.apache.commons.lang3.StringUtils;
23 -import com.fasterxml.jackson.databind.JsonNode; 24 +import org.thingsboard.server.common.data.ShortCustomerInfo;
24 import org.thingsboard.server.common.data.UUIDConverter; 25 import org.thingsboard.server.common.data.UUIDConverter;
25 import org.thingsboard.server.common.data.id.CustomerId; 26 import org.thingsboard.server.common.data.id.CustomerId;
26 import org.thingsboard.server.common.data.id.DashboardId; 27 import org.thingsboard.server.common.data.id.DashboardId;
@@ -54,7 +55,8 @@ public class DatabaseHelper { @@ -54,7 +55,8 @@ public class DatabaseHelper {
54 public static final ObjectMapper objectMapper = new ObjectMapper(); 55 public static final ObjectMapper objectMapper = new ObjectMapper();
55 56
56 public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception { 57 public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
57 - String[] columns = new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION}; 58 + JavaType assignedCustomersType =
  59 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
58 try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) { 60 try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) {
59 csvParser.forEach(record -> { 61 csvParser.forEach(record -> {
60 String customerIdString = record.get(CUSTOMER_ID); 62 String customerIdString = record.get(CUSTOMER_ID);
@@ -63,12 +65,11 @@ public class DatabaseHelper { @@ -63,12 +65,11 @@ public class DatabaseHelper {
63 List<CustomerId> customerIds = new ArrayList<>(); 65 List<CustomerId> customerIds = new ArrayList<>();
64 if (!StringUtils.isEmpty(assignedCustomersString)) { 66 if (!StringUtils.isEmpty(assignedCustomersString)) {
65 try { 67 try {
66 - JsonNode assignedCustomersJson = objectMapper.readTree(assignedCustomersString);  
67 - Map<String,String> assignedCustomers = objectMapper.treeToValue(assignedCustomersJson, HashMap.class);  
68 - assignedCustomers.forEach((strCustomerId, title) -> {  
69 - CustomerId customerId = new CustomerId(UUID.fromString(strCustomerId)); 68 + Set<ShortCustomerInfo> assignedCustomers = objectMapper.readValue(assignedCustomersString, assignedCustomersType);
  69 + assignedCustomers.forEach((customerInfo) -> {
  70 + CustomerId customerId = customerInfo.getCustomerId();
70 if (!customerId.isNullUid()) { 71 if (!customerId.isNullUid()) {
71 - customerIds.add(new CustomerId(UUID.fromString(strCustomerId))); 72 + customerIds.add(customerId);
72 } 73 }
73 }); 74 });
74 } catch (IOException e) { 75 } catch (IOException e) {
@@ -78,7 +79,7 @@ public class DatabaseHelper { @@ -78,7 +79,7 @@ public class DatabaseHelper {
78 if (!StringUtils.isEmpty(customerIdString)) { 79 if (!StringUtils.isEmpty(customerIdString)) {
79 CustomerId customerId = new CustomerId(toUUID(customerIdString, sql)); 80 CustomerId customerId = new CustomerId(toUUID(customerIdString, sql));
80 if (!customerId.isNullUid()) { 81 if (!customerId.isNullUid()) {
81 - customerIds.add(new CustomerId(toUUID(customerIdString, sql))); 82 + customerIds.add(customerId);
82 } 83 }
83 } 84 }
84 for (CustomerId customerId : customerIds) { 85 for (CustomerId customerId : customerIds) {
@@ -138,10 +138,10 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest @@ -138,10 +138,10 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
138 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() 138 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
139 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 139 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
140 140
141 - Assert.assertTrue(assignedDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString())); 141 + Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
142 142
143 Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 143 Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
144 - Assert.assertTrue(foundDashboard.getAssignedCustomers().containsKey(savedCustomer.getId().toString())); 144 + Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
145 145
146 Dashboard unassignedDashboard = 146 Dashboard unassignedDashboard =
147 doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); 147 doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
@@ -69,6 +69,11 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -69,6 +69,11 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
69 return false; 69 return false;
70 } 70 }
71 71
  72 + @JsonIgnore
  73 + public ShortCustomerInfo toShortCustomerInfo() {
  74 + return new ShortCustomerInfo(id, title, isPublic());
  75 + }
  76 +
72 @Override 77 @Override
73 @JsonProperty(access = Access.READ_ONLY) 78 @JsonProperty(access = Access.READ_ONLY)
74 public String getName() { 79 public String getName() {
@@ -20,16 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId; @@ -20,16 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.DashboardId; 20 import org.thingsboard.server.common.data.id.DashboardId;
21 import org.thingsboard.server.common.data.id.TenantId; 21 import org.thingsboard.server.common.data.id.TenantId;
22 22
23 -import java.util.HashMap;  
24 -import java.util.List;  
25 -import java.util.Map;  
26 -import java.util.Set; 23 +import java.util.*;
27 24
28 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName { 25 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
29 26
30 private TenantId tenantId; 27 private TenantId tenantId;
31 private String title; 28 private String title;
32 - private Map<String, String> assignedCustomers; 29 + private Set<ShortCustomerInfo> assignedCustomers;
33 30
34 public DashboardInfo() { 31 public DashboardInfo() {
35 super(); 32 super();
@@ -62,38 +59,56 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -62,38 +59,56 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
62 this.title = title; 59 this.title = title;
63 } 60 }
64 61
65 - public Map<String, String> getAssignedCustomers() { 62 + public Set<ShortCustomerInfo> getAssignedCustomers() {
66 return assignedCustomers; 63 return assignedCustomers;
67 } 64 }
68 65
69 - public void setAssignedCustomers(Map<String, String> assignedCustomers) { 66 + public void setAssignedCustomers(Set<ShortCustomerInfo> assignedCustomers) {
70 this.assignedCustomers = assignedCustomers; 67 this.assignedCustomers = assignedCustomers;
71 } 68 }
72 69
73 - public boolean addAssignedCustomer(CustomerId customerId, String title) {  
74 - if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) { 70 + public boolean isAssignedToCustomer(CustomerId customerId) {
  71 + return this.assignedCustomers != null && this.assignedCustomers.contains(new ShortCustomerInfo(customerId, null, false));
  72 + }
  73 +
  74 + public ShortCustomerInfo getAssignedCustomerInfo(CustomerId customerId) {
  75 + if (this.assignedCustomers != null) {
  76 + for (ShortCustomerInfo customerInfo : this.assignedCustomers) {
  77 + if (customerInfo.getCustomerId().equals(customerId)) {
  78 + return customerInfo;
  79 + }
  80 + }
  81 + }
  82 + return null;
  83 + }
  84 +
  85 + public boolean addAssignedCustomer(Customer customer) {
  86 + ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
  87 + if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
75 return false; 88 return false;
76 } else { 89 } else {
77 if (this.assignedCustomers == null) { 90 if (this.assignedCustomers == null) {
78 - this.assignedCustomers = new HashMap<>(); 91 + this.assignedCustomers = new HashSet<>();
79 } 92 }
80 - this.assignedCustomers.put(customerId.toString(), title); 93 + this.assignedCustomers.add(customerInfo);
81 return true; 94 return true;
82 } 95 }
83 } 96 }
84 97
85 - public boolean updateAssignedCustomer(CustomerId customerId, String title) {  
86 - if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {  
87 - this.assignedCustomers.put(customerId.toString(), title); 98 + public boolean updateAssignedCustomer(Customer customer) {
  99 + ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
  100 + if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  101 + this.assignedCustomers.add(customerInfo);
88 return true; 102 return true;
89 } else { 103 } else {
90 return false; 104 return false;
91 } 105 }
92 } 106 }
93 107
94 - public boolean removeAssignedCustomer(CustomerId customerId) {  
95 - if (this.assignedCustomers != null && this.assignedCustomers.containsKey(customerId.toString())) {  
96 - this.assignedCustomers.remove(customerId.toString()); 108 + public boolean removeAssignedCustomer(Customer customer) {
  109 + ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
  110 + if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
  111 + this.assignedCustomers.remove(customerInfo);
97 return true; 112 return true;
98 } else { 113 } else {
99 return false; 114 return false;
  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.common.data;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import org.thingsboard.server.common.data.id.CustomerId;
  22 +
  23 +/**
  24 + * Created by igor on 2/27/18.
  25 + */
  26 +
  27 +@AllArgsConstructor
  28 +public class ShortCustomerInfo {
  29 +
  30 + @Getter @Setter
  31 + private CustomerId customerId;
  32 +
  33 + @Getter @Setter
  34 + private String title;
  35 +
  36 + @Getter @Setter
  37 + private boolean isPublic;
  38 +
  39 + @Override
  40 + public boolean equals(Object o) {
  41 + if (this == o) return true;
  42 + if (o == null || getClass() != o.getClass()) return false;
  43 +
  44 + ShortCustomerInfo that = (ShortCustomerInfo) o;
  45 +
  46 + return customerId.equals(that.customerId);
  47 +
  48 + }
  49 +
  50 + @Override
  51 + public int hashCode() {
  52 + return customerId.hashCode();
  53 + }
  54 +}
@@ -98,7 +98,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @@ -98,7 +98,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
98 log.trace("Executing saveCustomer [{}]", customer); 98 log.trace("Executing saveCustomer [{}]", customer);
99 customerValidator.validate(customer); 99 customerValidator.validate(customer);
100 Customer savedCustomer = customerDao.save(customer); 100 Customer savedCustomer = customerDao.save(customer);
101 - dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle()); 101 + dashboardService.updateCustomerDashboards(savedCustomer.getId());
102 return savedCustomer; 102 return savedCustomer;
103 } 103 }
104 104
@@ -110,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @@ -110,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
110 if (customer == null) { 110 if (customer == null) {
111 throw new IncorrectParameterException("Unable to delete non-existent customer."); 111 throw new IncorrectParameterException("Unable to delete non-existent customer.");
112 } 112 }
113 - dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId); 113 + dashboardService.unassignCustomerDashboards(customerId);
114 assetService.unassignCustomerAssets(customer.getTenantId(), customerId); 114 assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
115 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId); 115 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
116 userService.deleteCustomerUsers(customer.getTenantId(), customerId); 116 userService.deleteCustomerUsers(customer.getTenantId(), customerId);
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.page.TextPageLink; @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
26 import org.thingsboard.server.common.data.page.TimePageData; 26 import org.thingsboard.server.common.data.page.TimePageData;
27 import org.thingsboard.server.common.data.page.TimePageLink; 27 import org.thingsboard.server.common.data.page.TimePageLink;
28 28
29 -import java.sql.Time; 29 +import java.util.Set;
30 30
31 public interface DashboardService { 31 public interface DashboardService {
32 32
@@ -52,8 +52,8 @@ public interface DashboardService { @@ -52,8 +52,8 @@ public interface DashboardService {
52 52
53 ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); 53 ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
54 54
55 - void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); 55 + void unassignCustomerDashboards(CustomerId customerId);
56 56
57 - void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle); 57 + void updateCustomerDashboards(CustomerId customerId);
58 58
59 } 59 }
@@ -117,7 +117,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -117,7 +117,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
117 if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) { 117 if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
118 throw new DataValidationException("Can't assign dashboard to customer from different tenant!"); 118 throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
119 } 119 }
120 - if (dashboard.addAssignedCustomer(customerId, customer.getTitle())) { 120 + if (dashboard.addAssignedCustomer(customer)) {
121 try { 121 try {
122 createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD)); 122 createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
123 } catch (ExecutionException | InterruptedException e) { 123 } catch (ExecutionException | InterruptedException e) {
@@ -133,7 +133,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -133,7 +133,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
133 @Override 133 @Override
134 public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) { 134 public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
135 Dashboard dashboard = findDashboardById(dashboardId); 135 Dashboard dashboard = findDashboardById(dashboardId);
136 - if (dashboard.removeAssignedCustomer(customerId)) { 136 + Customer customer = customerDao.findById(customerId.getId());
  137 + if (customer == null) {
  138 + throw new DataValidationException("Can't unassign dashboard from non-existent customer!");
  139 + }
  140 + if (dashboard.removeAssignedCustomer(customer)) {
137 try { 141 try {
138 deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD)); 142 deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
139 } catch (ExecutionException | InterruptedException e) { 143 } catch (ExecutionException | InterruptedException e) {
@@ -146,9 +150,9 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -146,9 +150,9 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
146 } 150 }
147 } 151 }
148 152
149 - private Dashboard updateAssignedCustomerTitle(DashboardId dashboardId, CustomerId customerId, String customerTitle) { 153 + private Dashboard updateAssignedCustomer(DashboardId dashboardId, Customer customer) {
150 Dashboard dashboard = findDashboardById(dashboardId); 154 Dashboard dashboard = findDashboardById(dashboardId);
151 - if (dashboard.updateAssignedCustomer(customerId, customerTitle)) { 155 + if (dashboard.updateAssignedCustomer(customer)) {
152 return saveDashboard(dashboard); 156 return saveDashboard(dashboard);
153 } else { 157 } else {
154 return dashboard; 158 return dashboard;
@@ -207,20 +211,25 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -207,20 +211,25 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
207 } 211 }
208 212
209 @Override 213 @Override
210 - public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) {  
211 - log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);  
212 - Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 214 + public void unassignCustomerDashboards(CustomerId customerId) {
  215 + log.trace("Executing unassignCustomerDashboards, customerId [{}]", customerId);
213 Validator.validateId(customerId, "Incorrect customerId " + customerId); 216 Validator.validateId(customerId, "Incorrect customerId " + customerId);
214 - new CustomerDashboardsUnassigner(tenantId, customerId).removeEntities(customerId); 217 + Customer customer = customerDao.findById(customerId.getId());
  218 + if (customer == null) {
  219 + throw new DataValidationException("Can't unassign dashboards from non-existent customer!");
  220 + }
  221 + new CustomerDashboardsUnassigner(customer).removeEntities(customer);
215 } 222 }
216 223
217 @Override 224 @Override
218 - public void updateCustomerDashboards(TenantId tenantId, CustomerId customerId, String customerTitle) {  
219 - log.trace("Executing updateCustomerDashboards, tenantId [{}], customerId [{}], customerTitle [{}]", tenantId, customerId, customerTitle);  
220 - Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); 225 + public void updateCustomerDashboards(CustomerId customerId) {
  226 + log.trace("Executing updateCustomerDashboards, customerId [{}]", customerId);
221 Validator.validateId(customerId, "Incorrect customerId " + customerId); 227 Validator.validateId(customerId, "Incorrect customerId " + customerId);
222 - Validator.validateString(customerTitle, "Incorrect customerTitle " + customerTitle);  
223 - new CustomerDashboardsUpdater(tenantId, customerId, customerTitle).removeEntities(customerId); 228 + Customer customer = customerDao.findById(customerId.getId());
  229 + if (customer == null) {
  230 + throw new DataValidationException("Can't update dashboards for non-existent customer!");
  231 + }
  232 + new CustomerDashboardsUpdater(customer).removeEntities(customer);
224 } 233 }
225 234
226 private DataValidator<Dashboard> dashboardValidator = 235 private DataValidator<Dashboard> dashboardValidator =
@@ -255,58 +264,52 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @@ -255,58 +264,52 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
255 } 264 }
256 }; 265 };
257 266
258 - private class CustomerDashboardsUnassigner extends TimePaginatedRemover<CustomerId, DashboardInfo> {  
259 -  
260 - private TenantId tenantId;  
261 - private CustomerId customerId; 267 + private class CustomerDashboardsUnassigner extends TimePaginatedRemover<Customer, DashboardInfo> {
262 268
263 - CustomerDashboardsUnassigner(TenantId tenantId, CustomerId customerId) {  
264 - this.tenantId = tenantId;  
265 - this.customerId = customerId; 269 + private Customer customer;
  270 +
  271 + CustomerDashboardsUnassigner(Customer customer) {
  272 + this.customer = customer;
266 } 273 }
267 274
268 @Override 275 @Override
269 - protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) { 276 + protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
270 try { 277 try {
271 - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get(); 278 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
272 } catch (InterruptedException | ExecutionException e) { 279 } catch (InterruptedException | ExecutionException e) {
273 - log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id); 280 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
274 throw new RuntimeException(e); 281 throw new RuntimeException(e);
275 } 282 }
276 } 283 }
277 284
278 @Override 285 @Override
279 protected void removeEntity(DashboardInfo entity) { 286 protected void removeEntity(DashboardInfo entity) {
280 - unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customerId); 287 + unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customer.getId());
281 } 288 }
282 289
283 } 290 }
284 291
285 - private class CustomerDashboardsUpdater extends TimePaginatedRemover<CustomerId, DashboardInfo> { 292 + private class CustomerDashboardsUpdater extends TimePaginatedRemover<Customer, DashboardInfo> {
286 293
287 - private TenantId tenantId;  
288 - private CustomerId customerId;  
289 - private String customerTitle; 294 + private Customer customer;
290 295
291 - CustomerDashboardsUpdater(TenantId tenantId, CustomerId customerId, String customerTitle) {  
292 - this.tenantId = tenantId;  
293 - this.customerId = customerId;  
294 - this.customerTitle = customerTitle; 296 + CustomerDashboardsUpdater(Customer customer) {
  297 + this.customer = customer;
295 } 298 }
296 299
297 @Override 300 @Override
298 - protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) { 301 + protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
299 try { 302 try {
300 - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink).get(); 303 + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
301 } catch (InterruptedException | ExecutionException e) { 304 } catch (InterruptedException | ExecutionException e) {
302 - log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", tenantId, id); 305 + log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
303 throw new RuntimeException(e); 306 throw new RuntimeException(e);
304 } 307 }
305 } 308 }
306 309
307 @Override 310 @Override
308 protected void removeEntity(DashboardInfo entity) { 311 protected void removeEntity(DashboardInfo entity) {
309 - updateAssignedCustomerTitle(new DashboardId(entity.getUuidId()), this.customerId, this.customerTitle); 312 + updateAssignedCustomer(new DashboardId(entity.getUuidId()), this.customer);
310 } 313 }
311 314
312 } 315 }
@@ -20,18 +20,22 @@ import com.datastax.driver.mapping.annotations.Column; @@ -20,18 +20,22 @@ import com.datastax.driver.mapping.annotations.Column;
20 import com.datastax.driver.mapping.annotations.PartitionKey; 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 import com.datastax.driver.mapping.annotations.Table; 21 import com.datastax.driver.mapping.annotations.Table;
22 import com.fasterxml.jackson.core.JsonProcessingException; 22 import com.fasterxml.jackson.core.JsonProcessingException;
  23 +import com.fasterxml.jackson.databind.JavaType;
23 import com.fasterxml.jackson.databind.JsonNode; 24 import com.fasterxml.jackson.databind.JsonNode;
24 import com.fasterxml.jackson.databind.ObjectMapper; 25 import com.fasterxml.jackson.databind.ObjectMapper;
25 import lombok.EqualsAndHashCode; 26 import lombok.EqualsAndHashCode;
26 import lombok.ToString; 27 import lombok.ToString;
27 import lombok.extern.slf4j.Slf4j; 28 import lombok.extern.slf4j.Slf4j;
  29 +import org.springframework.util.StringUtils;
28 import org.thingsboard.server.common.data.Dashboard; 30 import org.thingsboard.server.common.data.Dashboard;
  31 +import org.thingsboard.server.common.data.ShortCustomerInfo;
29 import org.thingsboard.server.common.data.id.DashboardId; 32 import org.thingsboard.server.common.data.id.DashboardId;
30 import org.thingsboard.server.common.data.id.TenantId; 33 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.dao.model.SearchTextEntity; 34 import org.thingsboard.server.dao.model.SearchTextEntity;
32 import org.thingsboard.server.dao.model.type.JsonCodec; 35 import org.thingsboard.server.dao.model.type.JsonCodec;
33 36
34 -import java.util.HashMap; 37 +import java.io.IOException;
  38 +import java.util.HashSet;
35 import java.util.UUID; 39 import java.util.UUID;
36 40
37 import static org.thingsboard.server.dao.model.ModelConstants.*; 41 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -43,6 +47,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -43,6 +47,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
43 public final class DashboardEntity implements SearchTextEntity<Dashboard> { 47 public final class DashboardEntity implements SearchTextEntity<Dashboard> {
44 48
45 private static final ObjectMapper objectMapper = new ObjectMapper(); 49 private static final ObjectMapper objectMapper = new ObjectMapper();
  50 + private static final JavaType assignedCustomersType =
  51 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
46 52
47 @PartitionKey(value = 0) 53 @PartitionKey(value = 0)
48 @Column(name = ID_PROPERTY) 54 @Column(name = ID_PROPERTY)
@@ -58,8 +64,8 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -58,8 +64,8 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
58 @Column(name = SEARCH_TEXT_PROPERTY) 64 @Column(name = SEARCH_TEXT_PROPERTY)
59 private String searchText; 65 private String searchText;
60 66
61 - @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)  
62 - private JsonNode assignedCustomers; 67 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  68 + private String assignedCustomers;
63 69
64 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) 70 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
65 private JsonNode configuration; 71 private JsonNode configuration;
@@ -77,7 +83,11 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -77,7 +83,11 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
77 } 83 }
78 this.title = dashboard.getTitle(); 84 this.title = dashboard.getTitle();
79 if (dashboard.getAssignedCustomers() != null) { 85 if (dashboard.getAssignedCustomers() != null) {
80 - this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers()); 86 + try {
  87 + this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
  88 + } catch (JsonProcessingException e) {
  89 + log.error("Unable to serialize assigned customers to string!", e);
  90 + }
81 } 91 }
82 this.configuration = dashboard.getConfiguration(); 92 this.configuration = dashboard.getConfiguration();
83 } 93 }
@@ -106,11 +116,11 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -106,11 +116,11 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
106 this.title = title; 116 this.title = title;
107 } 117 }
108 118
109 - public JsonNode getAssignedCustomers() { 119 + public String getAssignedCustomers() {
110 return assignedCustomers; 120 return assignedCustomers;
111 } 121 }
112 122
113 - public void setAssignedCustomers(JsonNode assignedCustomers) { 123 + public void setAssignedCustomers(String assignedCustomers) {
114 this.assignedCustomers = assignedCustomers; 124 this.assignedCustomers = assignedCustomers;
115 } 125 }
116 126
@@ -144,10 +154,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> { @@ -144,10 +154,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
144 dashboard.setTenantId(new TenantId(tenantId)); 154 dashboard.setTenantId(new TenantId(tenantId));
145 } 155 }
146 dashboard.setTitle(title); 156 dashboard.setTitle(title);
147 - if (assignedCustomers != null) { 157 + if (!StringUtils.isEmpty(assignedCustomers)) {
148 try { 158 try {
149 - dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));  
150 - } catch (JsonProcessingException e) { 159 + dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  160 + } catch (IOException e) {
151 log.warn("Unable to parse assigned customers!", e); 161 log.warn("Unable to parse assigned customers!", e);
152 } 162 }
153 } 163 }
@@ -20,20 +20,20 @@ import com.datastax.driver.mapping.annotations.Column; @@ -20,20 +20,20 @@ import com.datastax.driver.mapping.annotations.Column;
20 import com.datastax.driver.mapping.annotations.PartitionKey; 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 import com.datastax.driver.mapping.annotations.Table; 21 import com.datastax.driver.mapping.annotations.Table;
22 import com.fasterxml.jackson.core.JsonProcessingException; 22 import com.fasterxml.jackson.core.JsonProcessingException;
23 -import com.fasterxml.jackson.databind.JsonNode; 23 +import com.fasterxml.jackson.databind.JavaType;
24 import com.fasterxml.jackson.databind.ObjectMapper; 24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import lombok.EqualsAndHashCode; 25 import lombok.EqualsAndHashCode;
26 import lombok.ToString; 26 import lombok.ToString;
27 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
  28 +import org.springframework.util.StringUtils;
28 import org.thingsboard.server.common.data.DashboardInfo; 29 import org.thingsboard.server.common.data.DashboardInfo;
29 -import org.thingsboard.server.common.data.id.CustomerId; 30 +import org.thingsboard.server.common.data.ShortCustomerInfo;
30 import org.thingsboard.server.common.data.id.DashboardId; 31 import org.thingsboard.server.common.data.id.DashboardId;
31 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
32 import org.thingsboard.server.dao.model.SearchTextEntity; 33 import org.thingsboard.server.dao.model.SearchTextEntity;
33 -import org.thingsboard.server.dao.model.type.JsonCodec;  
34 34
35 -import java.util.HashMap;  
36 -import java.util.Set; 35 +import java.io.IOException;
  36 +import java.util.HashSet;
37 import java.util.UUID; 37 import java.util.UUID;
38 38
39 import static org.thingsboard.server.dao.model.ModelConstants.*; 39 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -45,6 +45,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*; @@ -45,6 +45,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
45 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { 45 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
46 46
47 private static final ObjectMapper objectMapper = new ObjectMapper(); 47 private static final ObjectMapper objectMapper = new ObjectMapper();
  48 + private static final JavaType assignedCustomersType =
  49 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
48 50
49 @PartitionKey(value = 0) 51 @PartitionKey(value = 0)
50 @Column(name = ID_PROPERTY) 52 @Column(name = ID_PROPERTY)
@@ -60,8 +62,8 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -60,8 +62,8 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
60 @Column(name = SEARCH_TEXT_PROPERTY) 62 @Column(name = SEARCH_TEXT_PROPERTY)
61 private String searchText; 63 private String searchText;
62 64
63 - @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY, codec = JsonCodec.class)  
64 - private JsonNode assignedCustomers; 65 + @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
  66 + private String assignedCustomers;
65 67
66 public DashboardInfoEntity() { 68 public DashboardInfoEntity() {
67 super(); 69 super();
@@ -76,7 +78,11 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -76,7 +78,11 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
76 } 78 }
77 this.title = dashboardInfo.getTitle(); 79 this.title = dashboardInfo.getTitle();
78 if (dashboardInfo.getAssignedCustomers() != null) { 80 if (dashboardInfo.getAssignedCustomers() != null) {
79 - this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers()); 81 + try {
  82 + this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
  83 + } catch (JsonProcessingException e) {
  84 + log.error("Unable to serialize assigned customers to string!", e);
  85 + }
80 } 86 }
81 } 87 }
82 88
@@ -104,11 +110,11 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -104,11 +110,11 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
104 this.title = title; 110 this.title = title;
105 } 111 }
106 112
107 - public JsonNode getAssignedCustomers() { 113 + public String getAssignedCustomers() {
108 return assignedCustomers; 114 return assignedCustomers;
109 } 115 }
110 116
111 - public void setAssignedCustomers(JsonNode assignedCustomers) { 117 + public void setAssignedCustomers(String assignedCustomers) {
112 this.assignedCustomers = assignedCustomers; 118 this.assignedCustomers = assignedCustomers;
113 } 119 }
114 120
@@ -134,10 +140,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> { @@ -134,10 +140,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
134 dashboardInfo.setTenantId(new TenantId(tenantId)); 140 dashboardInfo.setTenantId(new TenantId(tenantId));
135 } 141 }
136 dashboardInfo.setTitle(title); 142 dashboardInfo.setTitle(title);
137 - if (assignedCustomers != null) { 143 + if (!StringUtils.isEmpty(assignedCustomers)) {
138 try { 144 try {
139 - dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));  
140 - } catch (JsonProcessingException e) { 145 + dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  146 + } catch (IOException e) {
141 log.warn("Unable to parse assigned customers!", e); 147 log.warn("Unable to parse assigned customers!", e);
142 } 148 }
143 } 149 }
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.model.sql; @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.model.sql;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
19 import com.fasterxml.jackson.core.JsonProcessingException; 19 import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.JavaType;
20 import com.fasterxml.jackson.databind.JsonNode; 21 import com.fasterxml.jackson.databind.JsonNode;
21 import com.fasterxml.jackson.databind.ObjectMapper; 22 import com.fasterxml.jackson.databind.ObjectMapper;
22 import lombok.Data; 23 import lombok.Data;
@@ -24,8 +25,9 @@ import lombok.EqualsAndHashCode; @@ -24,8 +25,9 @@ import lombok.EqualsAndHashCode;
24 import lombok.extern.slf4j.Slf4j; 25 import lombok.extern.slf4j.Slf4j;
25 import org.hibernate.annotations.Type; 26 import org.hibernate.annotations.Type;
26 import org.hibernate.annotations.TypeDef; 27 import org.hibernate.annotations.TypeDef;
  28 +import org.springframework.util.StringUtils;
27 import org.thingsboard.server.common.data.Dashboard; 29 import org.thingsboard.server.common.data.Dashboard;
28 -import org.thingsboard.server.common.data.id.CustomerId; 30 +import org.thingsboard.server.common.data.ShortCustomerInfo;
29 import org.thingsboard.server.common.data.id.DashboardId; 31 import org.thingsboard.server.common.data.id.DashboardId;
30 import org.thingsboard.server.common.data.id.TenantId; 32 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.dao.model.BaseSqlEntity; 33 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -36,9 +38,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -36,9 +38,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
36 import javax.persistence.Column; 38 import javax.persistence.Column;
37 import javax.persistence.Entity; 39 import javax.persistence.Entity;
38 import javax.persistence.Table; 40 import javax.persistence.Table;
39 -import java.util.HashMap;  
40 -import java.util.List;  
41 -import java.util.Set; 41 +import java.io.IOException;
  42 +import java.util.HashSet;
42 43
43 @Data 44 @Data
44 @Slf4j 45 @Slf4j
@@ -49,6 +50,8 @@ import java.util.Set; @@ -49,6 +50,8 @@ import java.util.Set;
49 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> { 50 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
50 51
51 private static final ObjectMapper objectMapper = new ObjectMapper(); 52 private static final ObjectMapper objectMapper = new ObjectMapper();
  53 + private static final JavaType assignedCustomersType =
  54 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
52 55
53 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) 56 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
54 private String tenantId; 57 private String tenantId;
@@ -59,9 +62,8 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -59,9 +62,8 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
59 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) 62 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
60 private String searchText; 63 private String searchText;
61 64
62 - @Type(type = "json")  
63 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) 65 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
64 - private JsonNode assignedCustomers; 66 + private String assignedCustomers;
65 67
66 @Type(type = "json") 68 @Type(type = "json")
67 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) 69 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
@@ -80,7 +82,11 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -80,7 +82,11 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
80 } 82 }
81 this.title = dashboard.getTitle(); 83 this.title = dashboard.getTitle();
82 if (dashboard.getAssignedCustomers() != null) { 84 if (dashboard.getAssignedCustomers() != null) {
83 - this.assignedCustomers = objectMapper.valueToTree(dashboard.getAssignedCustomers()); 85 + try {
  86 + this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
  87 + } catch (JsonProcessingException e) {
  88 + log.error("Unable to serialize assigned customers to string!", e);
  89 + }
84 } 90 }
85 this.configuration = dashboard.getConfiguration(); 91 this.configuration = dashboard.getConfiguration();
86 } 92 }
@@ -103,10 +109,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S @@ -103,10 +109,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
103 dashboard.setTenantId(new TenantId(toUUID(tenantId))); 109 dashboard.setTenantId(new TenantId(toUUID(tenantId)));
104 } 110 }
105 dashboard.setTitle(title); 111 dashboard.setTitle(title);
106 - if (assignedCustomers != null) { 112 + if (!StringUtils.isEmpty(assignedCustomers)) {
107 try { 113 try {
108 - dashboard.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));  
109 - } catch (JsonProcessingException e) { 114 + dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  115 + } catch (IOException e) {
110 log.warn("Unable to parse assigned customers!", e); 116 log.warn("Unable to parse assigned customers!", e);
111 } 117 }
112 } 118 }
@@ -17,14 +17,14 @@ package org.thingsboard.server.dao.model.sql; @@ -17,14 +17,14 @@ package org.thingsboard.server.dao.model.sql;
17 17
18 import com.datastax.driver.core.utils.UUIDs; 18 import com.datastax.driver.core.utils.UUIDs;
19 import com.fasterxml.jackson.core.JsonProcessingException; 19 import com.fasterxml.jackson.core.JsonProcessingException;
20 -import com.fasterxml.jackson.databind.JsonNode; 20 +import com.fasterxml.jackson.databind.JavaType;
21 import com.fasterxml.jackson.databind.ObjectMapper; 21 import com.fasterxml.jackson.databind.ObjectMapper;
22 import lombok.Data; 22 import lombok.Data;
23 import lombok.EqualsAndHashCode; 23 import lombok.EqualsAndHashCode;
24 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
25 -import org.hibernate.annotations.Type; 25 +import org.springframework.util.StringUtils;
26 import org.thingsboard.server.common.data.DashboardInfo; 26 import org.thingsboard.server.common.data.DashboardInfo;
27 -import org.thingsboard.server.common.data.id.CustomerId; 27 +import org.thingsboard.server.common.data.ShortCustomerInfo;
28 import org.thingsboard.server.common.data.id.DashboardId; 28 import org.thingsboard.server.common.data.id.DashboardId;
29 import org.thingsboard.server.common.data.id.TenantId; 29 import org.thingsboard.server.common.data.id.TenantId;
30 import org.thingsboard.server.dao.model.BaseSqlEntity; 30 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -34,8 +34,8 @@ import org.thingsboard.server.dao.model.SearchTextEntity; @@ -34,8 +34,8 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
34 import javax.persistence.Column; 34 import javax.persistence.Column;
35 import javax.persistence.Entity; 35 import javax.persistence.Entity;
36 import javax.persistence.Table; 36 import javax.persistence.Table;
37 -import java.util.HashMap;  
38 -import java.util.Set; 37 +import java.io.IOException;
  38 +import java.util.HashSet;
39 39
40 @Data 40 @Data
41 @Slf4j 41 @Slf4j
@@ -45,6 +45,8 @@ import java.util.Set; @@ -45,6 +45,8 @@ import java.util.Set;
45 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> { 45 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
46 46
47 private static final ObjectMapper objectMapper = new ObjectMapper(); 47 private static final ObjectMapper objectMapper = new ObjectMapper();
  48 + private static final JavaType assignedCustomersType =
  49 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
48 50
49 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY) 51 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
50 private String tenantId; 52 private String tenantId;
@@ -55,9 +57,8 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -55,9 +57,8 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
55 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) 57 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
56 private String searchText; 58 private String searchText;
57 59
58 - @Type(type = "json")  
59 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) 60 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
60 - private JsonNode assignedCustomers; 61 + private String assignedCustomers;
61 62
62 public DashboardInfoEntity() { 63 public DashboardInfoEntity() {
63 super(); 64 super();
@@ -72,7 +73,11 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -72,7 +73,11 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
72 } 73 }
73 this.title = dashboardInfo.getTitle(); 74 this.title = dashboardInfo.getTitle();
74 if (dashboardInfo.getAssignedCustomers() != null) { 75 if (dashboardInfo.getAssignedCustomers() != null) {
75 - this.assignedCustomers = objectMapper.valueToTree(dashboardInfo.getAssignedCustomers()); 76 + try {
  77 + this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
  78 + } catch (JsonProcessingException e) {
  79 + log.error("Unable to serialize assigned customers to string!", e);
  80 + }
76 } 81 }
77 } 82 }
78 83
@@ -98,10 +103,10 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements @@ -98,10 +103,10 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
98 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId))); 103 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
99 } 104 }
100 dashboardInfo.setTitle(title); 105 dashboardInfo.setTitle(title);
101 - if (assignedCustomers != null) { 106 + if (!StringUtils.isEmpty(assignedCustomers)) {
102 try { 107 try {
103 - dashboardInfo.setAssignedCustomers(objectMapper.treeToValue(assignedCustomers, HashMap.class));  
104 - } catch (JsonProcessingException e) { 108 + dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
  109 + } catch (IOException e) {
105 log.warn("Unable to parse assigned customers!", e); 110 log.warn("Unable to parse assigned customers!", e);
106 } 111 }
107 } 112 }
@@ -320,7 +320,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { @@ -320,7 +320,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
320 320
321 Assert.assertEquals(dashboards, loadedDashboards); 321 Assert.assertEquals(dashboards, loadedDashboards);
322 322
323 - dashboardService.unassignCustomerDashboards(tenantId, customerId); 323 + dashboardService.unassignCustomerDashboards(customerId);
324 324
325 pageLink = new TimePageLink(42); 325 pageLink = new TimePageLink(42);
326 pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); 326 pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
@@ -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($rootScope, $http, $q, $location, customerService) { 20 +function DashboardService($rootScope, $http, $q, $location, $filter) {
21 21
22 var stDiffPromise; 22 var stDiffPromise;
23 23
@@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
37 deleteDashboard: deleteDashboard, 37 deleteDashboard: deleteDashboard,
38 saveDashboard: saveDashboard, 38 saveDashboard: saveDashboard,
39 unassignDashboardFromCustomer: unassignDashboardFromCustomer, 39 unassignDashboardFromCustomer: unassignDashboardFromCustomer,
  40 + updateDashboardCustomers: updateDashboardCustomers,
  41 + addDashboardCustomers: addDashboardCustomers,
  42 + removeDashboardCustomers: removeDashboardCustomers,
40 makeDashboardPublic: makeDashboardPublic, 43 makeDashboardPublic: makeDashboardPublic,
  44 + makeDashboardPrivate: makeDashboardPrivate,
41 getPublicDashboardLink: getPublicDashboardLink 45 getPublicDashboardLink: getPublicDashboardLink
42 } 46 }
43 47
@@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
56 url += '&textOffset=' + pageLink.textOffset; 60 url += '&textOffset=' + pageLink.textOffset;
57 } 61 }
58 $http.get(url, config).then(function success(response) { 62 $http.get(url, config).then(function success(response) {
59 - deferred.resolve(response.data); 63 + deferred.resolve(prepareDashboards(response.data));
60 }, function fail() { 64 }, function fail() {
61 deferred.reject(); 65 deferred.reject();
62 }); 66 });
63 return deferred.promise; 67 return deferred.promise;
64 } 68 }
65 69
66 - function getTenantDashboards(pageLink, applyCustomersInfo, config) { 70 + function getTenantDashboards(pageLink, config) {
67 var deferred = $q.defer(); 71 var deferred = $q.defer();
68 var url = '/api/tenant/dashboards?limit=' + pageLink.limit; 72 var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
69 if (angular.isDefined(pageLink.textSearch)) { 73 if (angular.isDefined(pageLink.textSearch)) {
@@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
76 url += '&textOffset=' + pageLink.textOffset; 80 url += '&textOffset=' + pageLink.textOffset;
77 } 81 }
78 $http.get(url, config).then(function success(response) { 82 $http.get(url, config).then(function success(response) {
79 - if (applyCustomersInfo) {  
80 - customerService.applyAssignedCustomersInfo(response.data.data).then(  
81 - function success(data) {  
82 - response.data.data = data;  
83 - deferred.resolve(response.data);  
84 - },  
85 - function fail() {  
86 - deferred.reject();  
87 - }  
88 - );  
89 - } else {  
90 - deferred.resolve(response.data);  
91 - } 83 + deferred.resolve(prepareDashboards(response.data));
92 }, function fail() { 84 }, function fail() {
93 deferred.reject(); 85 deferred.reject();
94 }); 86 });
95 return deferred.promise; 87 return deferred.promise;
96 } 88 }
97 89
98 - function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) { 90 + function getCustomerDashboards(customerId, pageLink, config) {
99 var deferred = $q.defer(); 91 var deferred = $q.defer();
100 var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit; 92 var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
101 - if (angular.isDefined(pageLink.textSearch)) {  
102 - url += '&textSearch=' + pageLink.textSearch;  
103 - }  
104 if (angular.isDefined(pageLink.idOffset)) { 93 if (angular.isDefined(pageLink.idOffset)) {
105 - url += '&idOffset=' + pageLink.idOffset;  
106 - }  
107 - if (angular.isDefined(pageLink.textOffset)) {  
108 - url += '&textOffset=' + pageLink.textOffset; 94 + url += '&offset=' + pageLink.idOffset;
109 } 95 }
110 $http.get(url, config).then(function success(response) { 96 $http.get(url, config).then(function success(response) {
111 - if (applyCustomersInfo) {  
112 - customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(  
113 - function success(data) {  
114 - response.data.data = data;  
115 - deferred.resolve(response.data);  
116 - },  
117 - function fail() {  
118 - deferred.reject();  
119 - }  
120 - );  
121 - } else {  
122 - deferred.resolve(response.data); 97 + response.data = prepareDashboards(response.data);
  98 + if (pageLink.textSearch) {
  99 + response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch});
123 } 100 }
  101 + deferred.resolve(response.data);
124 }, function fail() { 102 }, function fail() {
125 deferred.reject(); 103 deferred.reject();
126 }); 104 });
@@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
151 var deferred = $q.defer(); 129 var deferred = $q.defer();
152 var url = '/api/dashboard/' + dashboardId; 130 var url = '/api/dashboard/' + dashboardId;
153 $http.get(url, null).then(function success(response) { 131 $http.get(url, null).then(function success(response) {
154 - deferred.resolve(response.data); 132 + deferred.resolve(prepareDashboard(response.data));
155 }, function fail() { 133 }, function fail() {
156 deferred.reject(); 134 deferred.reject();
157 }); 135 });
@@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
162 var deferred = $q.defer(); 140 var deferred = $q.defer();
163 var url = '/api/dashboard/info/' + dashboardId; 141 var url = '/api/dashboard/info/' + dashboardId;
164 $http.get(url, config).then(function success(response) { 142 $http.get(url, config).then(function success(response) {
165 - deferred.resolve(response.data); 143 + deferred.resolve(prepareDashboard(response.data));
166 }, function fail() { 144 }, function fail() {
167 deferred.reject(); 145 deferred.reject();
168 }); 146 });
@@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
172 function saveDashboard(dashboard) { 150 function saveDashboard(dashboard) {
173 var deferred = $q.defer(); 151 var deferred = $q.defer();
174 var url = '/api/dashboard'; 152 var url = '/api/dashboard';
175 - $http.post(url, dashboard).then(function success(response) {  
176 - deferred.resolve(response.data); 153 + $http.post(url, cleanDashboard(dashboard)).then(function success(response) {
  154 + deferred.resolve(prepareDashboard(response.data));
177 }, function fail() { 155 }, function fail() {
178 deferred.reject(); 156 deferred.reject();
179 }); 157 });
@@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
195 var deferred = $q.defer(); 173 var deferred = $q.defer();
196 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId; 174 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
197 $http.post(url, null).then(function success(response) { 175 $http.post(url, null).then(function success(response) {
198 - deferred.resolve(response.data); 176 + deferred.resolve(prepareDashboard(response.data));
199 }, function fail() { 177 }, function fail() {
200 deferred.reject(); 178 deferred.reject();
201 }); 179 });
202 return deferred.promise; 180 return deferred.promise;
203 } 181 }
204 182
205 - function unassignDashboardFromCustomer(dashboardId) { 183 + function unassignDashboardFromCustomer(customerId, dashboardId) {
206 var deferred = $q.defer(); 184 var deferred = $q.defer();
207 - var url = '/api/customer/dashboard/' + dashboardId; 185 + var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
208 $http.delete(url).then(function success(response) { 186 $http.delete(url).then(function success(response) {
209 - deferred.resolve(response.data); 187 + deferred.resolve(prepareDashboard(response.data));
  188 + }, function fail() {
  189 + deferred.reject();
  190 + });
  191 + return deferred.promise;
  192 + }
  193 +
  194 + function updateDashboardCustomers(dashboardId, customerIds) {
  195 + var deferred = $q.defer();
  196 + var url = '/api/dashboard/' + dashboardId + '/customers';
  197 + $http.post(url, customerIds).then(function success(response) {
  198 + deferred.resolve(prepareDashboard(response.data));
  199 + }, function fail() {
  200 + deferred.reject();
  201 + });
  202 + return deferred.promise;
  203 + }
  204 +
  205 + function addDashboardCustomers(dashboardId, customerIds) {
  206 + var deferred = $q.defer();
  207 + var url = '/api/dashboard/' + dashboardId + '/customers/add';
  208 + $http.post(url, customerIds).then(function success(response) {
  209 + deferred.resolve(prepareDashboard(response.data));
  210 + }, function fail() {
  211 + deferred.reject();
  212 + });
  213 + return deferred.promise;
  214 + }
  215 +
  216 + function removeDashboardCustomers(dashboardId, customerIds) {
  217 + var deferred = $q.defer();
  218 + var url = '/api/dashboard/' + dashboardId + '/customers/remove';
  219 + $http.post(url, customerIds).then(function success(response) {
  220 + deferred.resolve(prepareDashboard(response.data));
210 }, function fail() { 221 }, function fail() {
211 deferred.reject(); 222 deferred.reject();
212 }); 223 });
@@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
217 var deferred = $q.defer(); 228 var deferred = $q.defer();
218 var url = '/api/customer/public/dashboard/' + dashboardId; 229 var url = '/api/customer/public/dashboard/' + dashboardId;
219 $http.post(url, null).then(function success(response) { 230 $http.post(url, null).then(function success(response) {
220 - deferred.resolve(response.data); 231 + deferred.resolve(prepareDashboard(response.data));
  232 + }, function fail() {
  233 + deferred.reject();
  234 + });
  235 + return deferred.promise;
  236 + }
  237 +
  238 + function makeDashboardPrivate(dashboardId) {
  239 + var deferred = $q.defer();
  240 + var url = '/api/customer/public/dashboard/' + dashboardId;
  241 + $http.delete(url).then(function success(response) {
  242 + deferred.resolve(prepareDashboard(response.data));
221 }, function fail() { 243 }, function fail() {
222 deferred.reject(); 244 deferred.reject();
223 }); 245 });
@@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) { @@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
230 if (port != 80 && port != 443) { 252 if (port != 80 && port != 443) {
231 url += ":" + port; 253 url += ":" + port;
232 } 254 }
233 - url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.customerId.id; 255 + url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
234 return url; 256 return url;
235 } 257 }
236 258
  259 + function prepareDashboards(dashboardsData) {
  260 + if (dashboardsData.data) {
  261 + for (var i = 0; i < dashboardsData.data.length; i++) {
  262 + dashboardsData.data[i] = prepareDashboard(dashboardsData.data[i]);
  263 + }
  264 + }
  265 + return dashboardsData;
  266 + }
  267 +
  268 + function prepareDashboard(dashboard) {
  269 + dashboard.publicCustomerId = null;
  270 + dashboard.assignedCustomersText = "";
  271 + dashboard.assignedCustomersIds = [];
  272 + if (dashboard.assignedCustomers && dashboard.assignedCustomers.length) {
  273 + var assignedCustomersTitles = [];
  274 + for (var i = 0; i < dashboard.assignedCustomers.length; i++) {
  275 + var assignedCustomer = dashboard.assignedCustomers[i];
  276 + dashboard.assignedCustomersIds.push(assignedCustomer.customerId.id);
  277 + if (assignedCustomer.public) {
  278 + dashboard.publicCustomerId = assignedCustomer.customerId.id;
  279 + } else {
  280 + assignedCustomersTitles.push(assignedCustomer.title);
  281 + }
  282 + }
  283 + dashboard.assignedCustomersText = assignedCustomersTitles.join(', ');
  284 + }
  285 + return dashboard;
  286 + }
  287 +
  288 + function cleanDashboard(dashboard) {
  289 + delete dashboard.publicCustomerId;
  290 + delete dashboard.assignedCustomersText;
  291 + delete dashboard.assignedCustomersIds;
  292 + return dashboard;
  293 + }
  294 +
237 } 295 }
@@ -273,9 +273,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -273,9 +273,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
273 break; 273 break;
274 case types.entityType.dashboard: 274 case types.entityType.dashboard:
275 if (user.authority === 'CUSTOMER_USER') { 275 if (user.authority === 'CUSTOMER_USER') {
276 - promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config); 276 + promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
277 } else { 277 } else {
278 - promise = dashboardService.getTenantDashboards(pageLink, false, config); 278 + promise = dashboardService.getTenantDashboards(pageLink, config);
279 } 279 }
280 break; 280 break;
281 case types.entityType.user: 281 case types.entityType.user:
@@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
403 return deferred.promise; 403 return deferred.promise;
404 } 404 }
405 405
  406 + function resolveAliasEntityId(entityType, id) {
  407 + var entityId = {
  408 + entityType: entityType,
  409 + id: id
  410 + };
  411 + if (entityType == types.aliasEntityType.current_customer) {
  412 + var user = userService.getCurrentUser();
  413 + entityId.entityType = types.entityType.customer;
  414 + if (user.authority === 'CUSTOMER_USER') {
  415 + entityId.id = user.customerId;
  416 + }
  417 + }
  418 + return entityId;
  419 + }
  420 +
406 function getStateEntityId(filter, stateParams) { 421 function getStateEntityId(filter, stateParams) {
407 var entityId = null; 422 var entityId = null;
408 if (stateParams) { 423 if (stateParams) {
@@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
417 if (!entityId) { 432 if (!entityId) {
418 entityId = filter.defaultStateEntity; 433 entityId = filter.defaultStateEntity;
419 } 434 }
  435 + if (entityId) {
  436 + entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
  437 + }
420 return entityId; 438 return entityId;
421 } 439 }
422 440
@@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
432 var stateEntityId = getStateEntityId(filter, stateParams); 450 var stateEntityId = getStateEntityId(filter, stateParams);
433 switch (filter.type) { 451 switch (filter.type) {
434 case types.aliasFilterType.singleEntity.value: 452 case types.aliasFilterType.singleEntity.value:
435 - getEntity(filter.singleEntity.entityType, filter.singleEntity.id, {ignoreLoading: true}).then( 453 + var aliasEntityId = resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
  454 + getEntity(aliasEntityId.entityType, aliasEntityId.id, {ignoreLoading: true}).then(
436 function success(entity) { 455 function success(entity) {
437 result.entities = entitiesToEntitiesInfo([entity]); 456 result.entities = entitiesToEntitiesInfo([entity]);
438 deferred.resolve(result); 457 deferred.resolve(result);
@@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
530 rootEntityId = filter.rootEntity.id; 549 rootEntityId = filter.rootEntity.id;
531 } 550 }
532 if (rootEntityType && rootEntityId) { 551 if (rootEntityType && rootEntityId) {
  552 + var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
533 var searchQuery = { 553 var searchQuery = {
534 parameters: { 554 parameters: {
535 - rootId: rootEntityId,  
536 - rootType: rootEntityType, 555 + rootId: relationQueryRootEntityId.id,
  556 + rootType: relationQueryRootEntityId.entityType,
537 direction: filter.direction 557 direction: filter.direction
538 }, 558 },
539 filters: filter.filters 559 filters: filter.filters
@@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
571 rootEntityId = filter.rootEntity.id; 591 rootEntityId = filter.rootEntity.id;
572 } 592 }
573 if (rootEntityType && rootEntityId) { 593 if (rootEntityType && rootEntityId) {
  594 + var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
574 searchQuery = { 595 searchQuery = {
575 parameters: { 596 parameters: {
576 - rootId: rootEntityId,  
577 - rootType: rootEntityType, 597 + rootId: searchQueryRootEntityId.id,
  598 + rootType: searchQueryRootEntityId.entityType,
578 direction: filter.direction 599 direction: filter.direction
579 }, 600 },
580 relationType: filter.relationType 601 relationType: filter.relationType
@@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
709 return result; 730 return result;
710 } 731 }
711 732
712 - function prepareAllowedEntityTypesList(allowedEntityTypes) { 733 + function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
713 var authority = userService.getAuthority(); 734 var authority = userService.getAuthority();
714 var entityTypes = {}; 735 var entityTypes = {};
715 switch(authority) { 736 switch(authority) {
@@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device @@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
726 entityTypes.rule = types.entityType.rule; 747 entityTypes.rule = types.entityType.rule;
727 entityTypes.plugin = types.entityType.plugin; 748 entityTypes.plugin = types.entityType.plugin;
728 entityTypes.dashboard = types.entityType.dashboard; 749 entityTypes.dashboard = types.entityType.dashboard;
  750 + if (useAliasEntityTypes) {
  751 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  752 + }
729 break; 753 break;
730 case 'CUSTOMER_USER': 754 case 'CUSTOMER_USER':
731 entityTypes.device = types.entityType.device; 755 entityTypes.device = types.entityType.device;
732 entityTypes.asset = types.entityType.asset; 756 entityTypes.asset = types.entityType.asset;
733 entityTypes.customer = types.entityType.customer; 757 entityTypes.customer = types.entityType.customer;
734 entityTypes.dashboard = types.entityType.dashboard; 758 entityTypes.dashboard = types.entityType.dashboard;
  759 + if (useAliasEntityTypes) {
  760 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  761 + }
735 break; 762 break;
736 } 763 }
737 764
@@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi @@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
266 var pageLink = {limit: 100}; 266 var pageLink = {limit: 100};
267 var fetchDashboardsPromise; 267 var fetchDashboardsPromise;
268 if (currentUser.authority === 'TENANT_ADMIN') { 268 if (currentUser.authority === 'TENANT_ADMIN') {
269 - fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false); 269 + fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
270 } else { 270 } else {
271 - fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false); 271 + fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
272 } 272 }
273 fetchDashboardsPromise.then( 273 fetchDashboardsPromise.then(
274 function success(result) { 274 function success(result) {
@@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', []) @@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', [])
296 dashboard: "DASHBOARD", 296 dashboard: "DASHBOARD",
297 alarm: "ALARM" 297 alarm: "ALARM"
298 }, 298 },
  299 + aliasEntityType: {
  300 + current_customer: "CURRENT_CUSTOMER"
  301 + },
299 entityTypeTranslations: { 302 entityTypeTranslations: {
300 "DEVICE": { 303 "DEVICE": {
301 type: 'entity.type-device', 304 type: 'entity.type-device',
@@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', []) @@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', [])
350 typePlural: 'entity.type-alarms', 353 typePlural: 'entity.type-alarms',
351 list: 'entity.list-of-alarms', 354 list: 'entity.list-of-alarms',
352 nameStartsWith: 'entity.alarm-name-starts-with' 355 nameStartsWith: 'entity.alarm-name-starts-with'
  356 + },
  357 + "CURRENT_CUSTOMER": {
  358 + type: 'entity.type-current-customer',
  359 + list: 'entity.type-current-customer'
353 } 360 }
354 }, 361 },
355 entitySearchDirection: { 362 entitySearchDirection: {
@@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u @@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
48 var promise; 48 var promise;
49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 if (scope.customerId) { 50 if (scope.customerId) {
51 - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); 51 + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
52 } else { 52 } else {
53 promise = $q.when({data: []}); 53 promise = $q.when({data: []});
54 } 54 }
@@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u @@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
60 promise = $q.when({data: []}); 60 promise = $q.when({data: []});
61 } 61 }
62 } else { 62 } else {
63 - promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); 63 + promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
64 } 64 }
65 } 65 }
66 66
@@ -48,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu @@ -48,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu
48 var promise; 48 var promise;
49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 if (scope.customerId && scope.customerId != types.id.nullUid) { 50 if (scope.customerId && scope.customerId != types.id.nullUid) {
51 - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); 51 + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
52 } else { 52 } else {
53 promise = $q.when({data: []}); 53 promise = $q.when({data: []});
54 } 54 }
55 } else { 55 } else {
56 - promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); 56 + promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
57 } 57 }
58 58
59 promise.then(function success(result) { 59 promise.then(function success(result) {
@@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD @@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD
52 fetchMoreItems_: function () { 52 fetchMoreItems_: function () {
53 if (vm.dashboards.hasNext && !vm.dashboards.pending) { 53 if (vm.dashboards.hasNext && !vm.dashboards.pending) {
54 vm.dashboards.pending = true; 54 vm.dashboards.pending = true;
55 - dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then( 55 + dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
56 function success(dashboards) { 56 function success(dashboards) {
57 vm.dashboards.data = vm.dashboards.data.concat(dashboards.data); 57 vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
58 vm.dashboards.nextPageLink = dashboards.nextPageLink; 58 vm.dashboards.nextPageLink = dashboards.nextPageLink;
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 -/*@ngInject*/  
17 -export default function AssignDashboardToCustomerController(customerService, dashboardService, $mdDialog, $q, dashboardIds, customers) {  
18 -  
19 - var vm = this;  
20 -  
21 - vm.customers = customers;  
22 - vm.searchText = '';  
23 -  
24 - vm.assign = assign;  
25 - vm.cancel = cancel;  
26 - vm.isCustomerSelected = isCustomerSelected;  
27 - vm.hasData = hasData;  
28 - vm.noData = noData;  
29 - vm.searchCustomerTextUpdated = searchCustomerTextUpdated;  
30 - vm.toggleCustomerSelection = toggleCustomerSelection;  
31 -  
32 - vm.theCustomers = {  
33 - getItemAtIndex: function (index) {  
34 - if (index > vm.customers.data.length) {  
35 - vm.theCustomers.fetchMoreItems_(index);  
36 - return null;  
37 - }  
38 - var item = vm.customers.data[index];  
39 - if (item) {  
40 - item.indexNumber = index + 1;  
41 - }  
42 - return item;  
43 - },  
44 -  
45 - getLength: function () {  
46 - if (vm.customers.hasNext) {  
47 - return vm.customers.data.length + vm.customers.nextPageLink.limit;  
48 - } else {  
49 - return vm.customers.data.length;  
50 - }  
51 - },  
52 -  
53 - fetchMoreItems_: function () {  
54 - if (vm.customers.hasNext && !vm.customers.pending) {  
55 - vm.customers.pending = true;  
56 - customerService.getCustomers(vm.customers.nextPageLink).then(  
57 - function success(customers) {  
58 - vm.customers.data = vm.customers.data.concat(customers.data);  
59 - vm.customers.nextPageLink = customers.nextPageLink;  
60 - vm.customers.hasNext = customers.hasNext;  
61 - if (vm.customers.hasNext) {  
62 - vm.customers.nextPageLink.limit = vm.customers.pageSize;  
63 - }  
64 - vm.customers.pending = false;  
65 - },  
66 - function fail() {  
67 - vm.customers.hasNext = false;  
68 - vm.customers.pending = false;  
69 - });  
70 - }  
71 - }  
72 - };  
73 -  
74 - function cancel () {  
75 - $mdDialog.cancel();  
76 - }  
77 -  
78 - function assign () {  
79 - var tasks = [];  
80 - for (var dashboardId in dashboardIds) {  
81 - tasks.push(dashboardService.assignDashboardToCustomer(vm.customers.selection.id.id, dashboardIds[dashboardId]));  
82 - }  
83 - $q.all(tasks).then(function () {  
84 - $mdDialog.hide();  
85 - });  
86 - }  
87 -  
88 - function noData () {  
89 - return vm.customers.data.length == 0 && !vm.customers.hasNext;  
90 - }  
91 -  
92 - function hasData () {  
93 - return vm.customers.data.length > 0;  
94 - }  
95 -  
96 - function toggleCustomerSelection ($event, customer) {  
97 - $event.stopPropagation();  
98 - if (vm.isCustomerSelected(customer)) {  
99 - vm.customers.selection = null;  
100 - } else {  
101 - vm.customers.selection = customer;  
102 - }  
103 - }  
104 -  
105 - function isCustomerSelected (customer) {  
106 - return vm.customers.selection != null && customer &&  
107 - customer.id.id === vm.customers.selection.id.id;  
108 - }  
109 -  
110 - function searchCustomerTextUpdated () {  
111 - vm.customers = {  
112 - pageSize: vm.customers.pageSize,  
113 - data: [],  
114 - nextPageLink: {  
115 - limit: vm.customers.pageSize,  
116 - textSearch: vm.searchText  
117 - },  
118 - selection: null,  
119 - hasNext: true,  
120 - pending: false  
121 - };  
122 - }  
123 -}  
  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 +.tb-dashboard-assigned-customers {
  18 + display: block;
  19 + display: -webkit-box;
  20 + height: 34px;
  21 + overflow: hidden;
  22 + text-overflow: ellipsis;
  23 + -webkit-line-clamp: 2;
  24 + -webkit-box-orient: vertical;
  25 + margin-bottom: 4px;
  26 +}
  27 +
@@ -15,6 +15,6 @@ @@ -15,6 +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.item.assignedCustomer.title}}'</div>  
19 -<div class="tb-small" ng-show="vm.isPublic()">{{'dashboard.public' | translate}}</div> 18 +<div class="tb-small tb-dashboard-assigned-customers" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomersText">{{'dashboard.assignedToCustomers' | translate}}: '{{vm.item.assignedCustomersText}}'</div>
  19 +<div class="tb-small" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.publicCustomerId">{{'dashboard.public' | translate}}</div>
20 20
@@ -19,24 +19,29 @@ @@ -19,24 +19,29 @@
19 ng-show="!isEdit && dashboardScope === 'tenant'" 19 ng-show="!isEdit && dashboardScope === 'tenant'"
20 class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> 20 class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
21 <md-button ng-click="onMakePublic({event: $event})" 21 <md-button ng-click="onMakePublic({event: $event})"
22 - ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer && !isPublic" 22 + ng-show="!isEdit && dashboardScope === 'tenant' && !dashboard.publicCustomerId"
23 class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button> 23 class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button>
24 -<md-button ng-click="onAssignToCustomer({event: $event})"  
25 - ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer"  
26 - class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>  
27 -<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})"  
28 - ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer"  
29 - class="md-raised md-primary">{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }}</md-button> 24 +<md-button ng-click="onMakePrivate({event: $event})"
  25 + ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
  26 + dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))"
  27 + class="md-raised md-primary">{{ 'dashboard.make-private' | translate }}</md-button>
  28 +<md-button ng-click="onManageAssignedCustomers({event: $event})"
  29 + ng-show="!isEdit && dashboardScope === 'tenant'"
  30 + class="md-raised md-primary">{{ 'dashboard.manage-assigned-customers' | translate }}</md-button>
  31 +<md-button ng-click="onUnassignFromCustomer({event: $event})"
  32 + ng-show="!isEdit && dashboardScope === 'customer' && customerId != dashboard.publicCustomerId"
  33 + class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
30 <md-button ng-click="onDeleteDashboard({event: $event})" 34 <md-button ng-click="onDeleteDashboard({event: $event})"
31 ng-show="!isEdit && dashboardScope === 'tenant'" 35 ng-show="!isEdit && dashboardScope === 'tenant'"
32 class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> 36 class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
33 <md-content class="md-padding" layout="column"> 37 <md-content class="md-padding" layout="column">
34 <md-input-container class="md-block" 38 <md-input-container class="md-block"
35 - ng-show="!isEdit && isAssignedToCustomer && !isPublic && dashboardScope === 'tenant'">  
36 - <label translate>dashboard.assignedToCustomer</label>  
37 - <input ng-model="assignedCustomer.title" disabled> 39 + ng-show="!isEdit && dashboard.assignedCustomersText && dashboardScope === 'tenant'">
  40 + <label translate>dashboard.assignedToCustomers</label>
  41 + <input ng-model="dashboard.assignedCustomersText" disabled>
38 </md-input-container> 42 </md-input-container>
39 - <div layout="column" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')"> 43 + <div layout="column" ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
  44 + dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))">
40 <tb-social-share-panel style="padding-bottom: 10px;" 45 <tb-social-share-panel style="padding-bottom: 10px;"
41 share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}" 46 share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
42 share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}" 47 share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
@@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html'; @@ -20,36 +20,17 @@ 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, $translate, types, toast, customerService, dashboardService) { 23 +export default function DashboardDirective($compile, $templateCache, $translate, types, toast, 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 -  
28 - scope.isAssignedToCustomer = false;  
29 - scope.isPublic = false;  
30 - scope.assignedCustomer = null;  
31 scope.publicLink = null; 27 scope.publicLink = null;
32 -  
33 scope.$watch('dashboard', function(newVal) { 28 scope.$watch('dashboard', function(newVal) {
34 if (newVal) { 29 if (newVal) {
35 - if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) {  
36 - scope.isAssignedToCustomer = true;  
37 - customerService.getShortCustomerInfo(scope.dashboard.customerId.id).then(  
38 - function success(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 - }  
46 - }  
47 - ); 30 + if (scope.dashboard.publicCustomerId) {
  31 + scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
48 } else { 32 } else {
49 - scope.isAssignedToCustomer = false;  
50 - scope.isPublic = false;  
51 scope.publicLink = null; 33 scope.publicLink = null;
52 - scope.assignedCustomer = null;  
53 } 34 }
54 } 35 }
55 }); 36 });
@@ -66,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate, @@ -66,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate,
66 scope: { 47 scope: {
67 dashboard: '=', 48 dashboard: '=',
68 isEdit: '=', 49 isEdit: '=',
  50 + customerId: '=',
69 dashboardScope: '=', 51 dashboardScope: '=',
70 theForm: '=', 52 theForm: '=',
71 - onAssignToCustomer: '&',  
72 onMakePublic: '&', 53 onMakePublic: '&',
  54 + onMakePrivate: '&',
  55 + onManageAssignedCustomers: '&',
73 onUnassignFromCustomer: '&', 56 onUnassignFromCustomer: '&',
74 onExportDashboard: '&', 57 onExportDashboard: '&',
75 onDeleteDashboard: '&' 58 onDeleteDashboard: '&'
@@ -17,12 +17,14 @@ @@ -17,12 +17,14 @@
17 17
18 import addDashboardTemplate from './add-dashboard.tpl.html'; 18 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';  
21 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html'; 20 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
22 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html'; 21 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html';
  22 +import manageAssignedCustomersTemplate from './manage-assigned-customers.tpl.html';
23 23
24 /* eslint-enable import/no-unresolved, import/default */ 24 /* eslint-enable import/no-unresolved, import/default */
25 25
  26 +import './dashboard-card.scss';
  27 +
26 /*@ngInject*/ 28 /*@ngInject*/
27 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) { 29 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) {
28 30
@@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast @@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast
48 export function DashboardCardController(types) { 50 export function DashboardCardController(types) {
49 51
50 var vm = this; 52 var vm = this;
51 -  
52 vm.types = types; 53 vm.types = types;
53 54
54 - vm.isAssignedToCustomer = function() {  
55 - if (vm.item && vm.item.customerId && vm.parentCtl.dashboardsScope === 'tenant' &&  
56 - vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) {  
57 - return true;  
58 - }  
59 - return false;  
60 - }  
61 -  
62 - vm.isPublic = function() {  
63 - if (vm.item && vm.item.assignedCustomer && vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomer.isPublic) {  
64 - return true;  
65 - }  
66 - return false;  
67 - }  
68 } 55 }
69 56
70 /*@ngInject*/ 57 /*@ngInject*/
@@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ
135 122
136 vm.dashboardsScope = $state.$current.data.dashboardsType; 123 vm.dashboardsScope = $state.$current.data.dashboardsType;
137 124
138 - vm.assignToCustomer = assignToCustomer;  
139 vm.makePublic = makePublic; 125 vm.makePublic = makePublic;
  126 + vm.makePrivate = makePrivate;
  127 + vm.manageAssignedCustomers = manageAssignedCustomers;
140 vm.unassignFromCustomer = unassignFromCustomer; 128 vm.unassignFromCustomer = unassignFromCustomer;
141 vm.exportDashboard = exportDashboard; 129 vm.exportDashboard = exportDashboard;
142 130
@@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ
155 } 143 }
156 144
157 if (customerId) { 145 if (customerId) {
  146 + vm.customerId = customerId;
158 vm.customerDashboardsTitle = $translate.instant('customer.dashboards'); 147 vm.customerDashboardsTitle = $translate.instant('customer.dashboards');
159 customerService.getShortCustomerInfo(customerId).then( 148 customerService.getShortCustomerInfo(customerId).then(
160 function success(info) { 149 function success(info) {
@@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ
167 156
168 if (vm.dashboardsScope === 'tenant') { 157 if (vm.dashboardsScope === 'tenant') {
169 fetchDashboardsFunction = function (pageLink) { 158 fetchDashboardsFunction = function (pageLink) {
170 - return dashboardService.getTenantDashboards(pageLink, true); 159 + return dashboardService.getTenantDashboards(pageLink);
171 }; 160 };
172 deleteDashboardFunction = function (dashboardId) { 161 deleteDashboardFunction = function (dashboardId) {
173 return dashboardService.deleteDashboard(dashboardId); 162 return dashboardService.deleteDashboard(dashboardId);
@@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ
194 details: function() { return $translate.instant('dashboard.make-public') }, 183 details: function() { return $translate.instant('dashboard.make-public') },
195 icon: "share", 184 icon: "share",
196 isEnabled: function(dashboard) { 185 isEnabled: function(dashboard) {
197 - return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); 186 + return dashboard && !dashboard.publicCustomerId;
198 } 187 }
199 }); 188 });
200 -  
201 dashboardActionsList.push({ 189 dashboardActionsList.push({
  190 + onAction: function ($event, item) {
  191 + makePrivate($event, item);
  192 + },
  193 + name: function() { return $translate.instant('action.make-private') },
  194 + details: function() { return $translate.instant('dashboard.make-private') },
  195 + icon: "reply",
  196 + isEnabled: function(dashboard) {
  197 + return dashboard && dashboard.publicCustomerId;
  198 + }
  199 + });
  200 + dashboardActionsList.push({
  201 + onAction: function ($event, item) {
  202 + manageAssignedCustomers($event, item);
  203 + },
  204 + name: function() { return $translate.instant('action.assign') },
  205 + details: function() { return $translate.instant('dashboard.manage-assigned-customers') },
  206 + icon: "assignment_ind",
  207 + isEnabled: function(dashboard) {
  208 + return dashboard;
  209 + }
  210 + });
  211 +
  212 + /*dashboardActionsList.push({
202 onAction: function ($event, item) { 213 onAction: function ($event, item) {
203 assignToCustomer($event, [ item.id.id ]); 214 assignToCustomer($event, [ item.id.id ]);
204 }, 215 },
@@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ
208 isEnabled: function(dashboard) { 219 isEnabled: function(dashboard) {
209 return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); 220 return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
210 } 221 }
211 - });  
212 - dashboardActionsList.push({ 222 + });*/
  223 + /*dashboardActionsList.push({
213 onAction: function ($event, item) { 224 onAction: function ($event, item) {
214 unassignFromCustomer($event, item, false); 225 unassignFromCustomer($event, item, false);
215 }, 226 },
@@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ
219 isEnabled: function(dashboard) { 230 isEnabled: function(dashboard) {
220 return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic; 231 return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic;
221 } 232 }
222 - });  
223 - dashboardActionsList.push({  
224 - onAction: function ($event, item) {  
225 - unassignFromCustomer($event, item, true);  
226 - },  
227 - name: function() { return $translate.instant('action.make-private') },  
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 - }); 233 + });*/
234 234
235 dashboardActionsList.push( 235 dashboardActionsList.push(
236 { 236 {
@@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ
246 dashboardGroupActionsList.push( 246 dashboardGroupActionsList.push(
247 { 247 {
248 onAction: function ($event, items) { 248 onAction: function ($event, items) {
249 - assignDashboardsToCustomer($event, items); 249 + assignDashboardsToCustomers($event, items);
250 }, 250 },
251 name: function() { return $translate.instant('dashboard.assign-dashboards') }, 251 name: function() { return $translate.instant('dashboard.assign-dashboards') },
252 details: function(selectedCount) { 252 details: function(selectedCount) {
@@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ
255 icon: "assignment_ind" 255 icon: "assignment_ind"
256 } 256 }
257 ); 257 );
  258 + dashboardGroupActionsList.push(
  259 + {
  260 + onAction: function ($event, items) {
  261 + unassignDashboardsFromCustomers($event, items);
  262 + },
  263 + name: function() { return $translate.instant('dashboard.unassign-dashboards') },
  264 + details: function(selectedCount) {
  265 + return $translate.instant('dashboard.unassign-dashboards-action-text', {count: selectedCount}, "messageformat");
  266 + },
  267 + icon: "assignment_return" }
  268 + );
258 269
259 dashboardGroupActionsList.push( 270 dashboardGroupActionsList.push(
260 { 271 {
@@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ
290 }); 301 });
291 } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') { 302 } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
292 fetchDashboardsFunction = function (pageLink) { 303 fetchDashboardsFunction = function (pageLink) {
293 - return dashboardService.getCustomerDashboards(customerId, pageLink, true); 304 + return dashboardService.getCustomerDashboards(customerId, pageLink);
294 }; 305 };
295 deleteDashboardFunction = function (dashboardId) { 306 deleteDashboardFunction = function (dashboardId) {
296 - return dashboardService.unassignDashboardFromCustomer(dashboardId); 307 + return dashboardService.unassignDashboardFromCustomer(customerId, dashboardId);
297 }; 308 };
298 refreshDashboardsParamsFunction = function () { 309 refreshDashboardsParamsFunction = function () {
299 return {"customerId": customerId, "topIndex": vm.topIndex}; 310 return {"customerId": customerId, "topIndex": vm.topIndex};
@@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ
314 dashboardActionsList.push( 325 dashboardActionsList.push(
315 { 326 {
316 onAction: function ($event, item) { 327 onAction: function ($event, item) {
317 - unassignFromCustomer($event, item, false); 328 + makePrivate($event, item);
318 }, 329 },
319 - name: function() { return $translate.instant('action.unassign') },  
320 - details: function() { return $translate.instant('dashboard.unassign-from-customer') },  
321 - icon: "assignment_return", 330 + name: function() { return $translate.instant('action.make-private') },
  331 + details: function() { return $translate.instant('dashboard.make-private') },
  332 + icon: "reply",
322 isEnabled: function(dashboard) { 333 isEnabled: function(dashboard) {
323 - return dashboard && !dashboard.assignedCustomer.isPublic; 334 + return dashboard && customerId == dashboard.publicCustomerId;
324 } 335 }
325 } 336 }
326 ); 337 );
  338 +
327 dashboardActionsList.push( 339 dashboardActionsList.push(
328 { 340 {
329 onAction: function ($event, item) { 341 onAction: function ($event, item) {
330 - unassignFromCustomer($event, item, true); 342 + unassignFromCustomer($event, item, customerId);
331 }, 343 },
332 - name: function() { return $translate.instant('action.make-private') },  
333 - details: function() { return $translate.instant('dashboard.make-private') },  
334 - icon: "reply", 344 + name: function() { return $translate.instant('action.unassign') },
  345 + details: function() { return $translate.instant('dashboard.unassign-from-customer') },
  346 + icon: "assignment_return",
335 isEnabled: function(dashboard) { 347 isEnabled: function(dashboard) {
336 - return dashboard && dashboard.assignedCustomer.isPublic; 348 + return dashboard && customerId != dashboard.publicCustomerId;
337 } 349 }
338 } 350 }
339 ); 351 );
@@ -341,7 +353,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -341,7 +353,7 @@ export function DashboardsController(userService, dashboardService, customerServ
341 dashboardGroupActionsList.push( 353 dashboardGroupActionsList.push(
342 { 354 {
343 onAction: function ($event, items) { 355 onAction: function ($event, items) {
344 - unassignDashboardsFromCustomer($event, items); 356 + unassignDashboardsFromCustomer($event, items, customerId);
345 }, 357 },
346 name: function() { return $translate.instant('dashboard.unassign-dashboards') }, 358 name: function() { return $translate.instant('dashboard.unassign-dashboards') },
347 details: function(selectedCount) { 359 details: function(selectedCount) {
@@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ
351 } 363 }
352 ); 364 );
353 365
354 -  
355 vm.dashboardGridConfig.addItemAction = { 366 vm.dashboardGridConfig.addItemAction = {
356 onAction: function ($event) { 367 onAction: function ($event) {
357 addDashboardsToCustomer($event); 368 addDashboardsToCustomer($event);
@@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ
428 return deferred.promise; 439 return deferred.promise;
429 } 440 }
430 441
431 - function assignToCustomer($event, dashboardIds) { 442 + function manageAssignedCustomers($event, dashboard) {
  443 + showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', dashboard.assignedCustomersIds);
  444 + }
  445 +
  446 + function assignDashboardsToCustomers($event, items) {
  447 + var dashboardIds = [];
  448 + for (var id in items.selections) {
  449 + dashboardIds.push(id);
  450 + }
  451 + showManageAssignedCustomersDialog($event, dashboardIds, 'assign');
  452 + }
  453 +
  454 + function unassignDashboardsFromCustomers($event, items) {
  455 + var dashboardIds = [];
  456 + for (var id in items.selections) {
  457 + dashboardIds.push(id);
  458 + }
  459 + showManageAssignedCustomersDialog($event, dashboardIds, 'unassign');
  460 + }
  461 +
  462 + function showManageAssignedCustomersDialog($event, dashboardIds, actionType, assignedCustomers) {
432 if ($event) { 463 if ($event) {
433 $event.stopPropagation(); 464 $event.stopPropagation();
434 } 465 }
435 - var pageSize = 10;  
436 - customerService.getCustomers({limit: pageSize, textSearch: ''}).then(  
437 - function success(_customers) {  
438 - var customers = {  
439 - pageSize: pageSize,  
440 - data: _customers.data,  
441 - nextPageLink: _customers.nextPageLink,  
442 - selection: null,  
443 - hasNext: _customers.hasNext,  
444 - pending: false  
445 - };  
446 - if (customers.hasNext) {  
447 - customers.nextPageLink.limit = pageSize;  
448 - }  
449 - $mdDialog.show({  
450 - controller: 'AssignDashboardToCustomerController',  
451 - controllerAs: 'vm',  
452 - templateUrl: assignToCustomerTemplate,  
453 - locals: {dashboardIds: dashboardIds, customers: customers},  
454 - parent: angular.element($document[0].body),  
455 - fullscreen: true,  
456 - targetEvent: $event  
457 - }).then(function () {  
458 - vm.grid.refreshList();  
459 - }, function () {  
460 - });  
461 - },  
462 - function fail() {  
463 - }); 466 + $mdDialog.show({
  467 + controller: 'ManageAssignedCustomersController',
  468 + controllerAs: 'vm',
  469 + templateUrl: manageAssignedCustomersTemplate,
  470 + locals: {actionType: actionType, dashboardIds: dashboardIds, assignedCustomers: assignedCustomers},
  471 + parent: angular.element($document[0].body),
  472 + fullscreen: true,
  473 + targetEvent: $event
  474 + }).then(function () {
  475 + vm.grid.refreshList();
  476 + }, function () {
  477 + });
464 } 478 }
465 479
466 function addDashboardsToCustomer($event) { 480 function addDashboardsToCustomer($event) {
@@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ
468 $event.stopPropagation(); 482 $event.stopPropagation();
469 } 483 }
470 var pageSize = 10; 484 var pageSize = 10;
471 - dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}, false).then( 485 + dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
472 function success(_dashboards) { 486 function success(_dashboards) {
473 var dashboards = { 487 var dashboards = {
474 pageSize: pageSize, 488 pageSize: pageSize,
@@ -499,30 +513,13 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -499,30 +513,13 @@ export function DashboardsController(userService, dashboardService, customerServ
499 }); 513 });
500 } 514 }
501 515
502 - function assignDashboardsToCustomer($event, items) {  
503 - var dashboardIds = [];  
504 - for (var id in items.selections) {  
505 - dashboardIds.push(id);  
506 - }  
507 - assignToCustomer($event, dashboardIds);  
508 - }  
509 -  
510 - function unassignFromCustomer($event, dashboard, isPublic) { 516 + function unassignFromCustomer($event, dashboard, customerId) {
511 if ($event) { 517 if ($event) {
512 $event.stopPropagation(); 518 $event.stopPropagation();
513 } 519 }
514 - var title;  
515 - var content;  
516 - var label;  
517 - if (isPublic) {  
518 - title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});  
519 - content = $translate.instant('dashboard.make-private-dashboard-text');  
520 - label = $translate.instant('dashboard.make-private-dashboard');  
521 - } else {  
522 - title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});  
523 - content = $translate.instant('dashboard.unassign-dashboard-text');  
524 - label = $translate.instant('dashboard.unassign-dashboard');  
525 - } 520 + var title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
  521 + var content = $translate.instant('dashboard.unassign-dashboard-text');
  522 + var label = $translate.instant('dashboard.unassign-dashboard');
526 var confirm = $mdDialog.confirm() 523 var confirm = $mdDialog.confirm()
527 .targetEvent($event) 524 .targetEvent($event)
528 .title(title) 525 .title(title)
@@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ
531 .cancel($translate.instant('action.no')) 528 .cancel($translate.instant('action.no'))
532 .ok($translate.instant('action.yes')); 529 .ok($translate.instant('action.yes'));
533 $mdDialog.show(confirm).then(function () { 530 $mdDialog.show(confirm).then(function () {
534 - dashboardService.unassignDashboardFromCustomer(dashboard.id.id).then(function success() { 531 + dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).then(function success() {
535 vm.grid.refreshList(); 532 vm.grid.refreshList();
536 }); 533 });
537 }); 534 });
@@ -556,12 +553,33 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -556,12 +553,33 @@ export function DashboardsController(userService, dashboardService, customerServ
556 }); 553 });
557 } 554 }
558 555
  556 + function makePrivate($event, dashboard) {
  557 + if ($event) {
  558 + $event.stopPropagation();
  559 + }
  560 + var title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
  561 + var content = $translate.instant('dashboard.make-private-dashboard-text');
  562 + var label = $translate.instant('dashboard.make-private-dashboard');
  563 + var confirm = $mdDialog.confirm()
  564 + .targetEvent($event)
  565 + .title(title)
  566 + .htmlContent(content)
  567 + .ariaLabel(label)
  568 + .cancel($translate.instant('action.no'))
  569 + .ok($translate.instant('action.yes'));
  570 + $mdDialog.show(confirm).then(function () {
  571 + dashboardService.makeDashboardPrivate(dashboard.id.id).then(function success() {
  572 + vm.grid.refreshList();
  573 + });
  574 + });
  575 + }
  576 +
559 function exportDashboard($event, dashboard) { 577 function exportDashboard($event, dashboard) {
560 $event.stopPropagation(); 578 $event.stopPropagation();
561 importExport.exportDashboard(dashboard.id.id); 579 importExport.exportDashboard(dashboard.id.id);
562 } 580 }
563 581
564 - function unassignDashboardsFromCustomer($event, items) { 582 + function unassignDashboardsFromCustomer($event, items, customerId) {
565 var confirm = $mdDialog.confirm() 583 var confirm = $mdDialog.confirm()
566 .targetEvent($event) 584 .targetEvent($event)
567 .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat')) 585 .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
@@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ @@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ
572 $mdDialog.show(confirm).then(function () { 590 $mdDialog.show(confirm).then(function () {
573 var tasks = []; 591 var tasks = [];
574 for (var id in items.selections) { 592 for (var id in items.selections) {
575 - tasks.push(dashboardService.unassignDashboardFromCustomer(id)); 593 + tasks.push(dashboardService.unassignDashboardFromCustomer(customerId, id));
576 } 594 }
577 $q.all(tasks).then(function () { 595 $q.all(tasks).then(function () {
578 vm.grid.refreshList(); 596 vm.grid.refreshList();
@@ -25,10 +25,12 @@ @@ -25,10 +25,12 @@
25 <tb-dashboard-details dashboard="vm.grid.operatingItem()" 25 <tb-dashboard-details dashboard="vm.grid.operatingItem()"
26 is-edit="vm.grid.detailsConfig.isDetailsEditMode" 26 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
27 dashboard-scope="vm.dashboardsScope" 27 dashboard-scope="vm.dashboardsScope"
  28 + customer-id="vm.customerId"
28 the-form="vm.grid.detailsForm" 29 the-form="vm.grid.detailsForm"
29 - on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"  
30 on-make-public="vm.makePublic(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-make-private="vm.makePrivate(event, vm.grid.detailsConfig.currentItem)"
  32 + on-manage-assigned-customers="vm.manageAssignedCustomers(event, vm.grid.detailsConfig.currentItem)"
  33 + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, vm.customerId)"
32 on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" 34 on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
33 on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> 35 on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
34 </md-tab> 36 </md-tab>
@@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes'; @@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes';
40 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller'; 40 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
41 import DashboardController from './dashboard.controller'; 41 import DashboardController from './dashboard.controller';
42 import DashboardSettingsController from './dashboard-settings.controller'; 42 import DashboardSettingsController from './dashboard-settings.controller';
43 -import AssignDashboardToCustomerController from './assign-to-customer.controller';  
44 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller'; 43 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
  44 +import ManageAssignedCustomersController from './manage-assigned-customers.controller';
45 import AddWidgetController from './add-widget.controller'; 45 import AddWidgetController from './add-widget.controller';
46 import DashboardDirective from './dashboard.directive'; 46 import DashboardDirective from './dashboard.directive';
47 import EditWidgetDirective from './edit-widget.directive'; 47 import EditWidgetDirective from './edit-widget.directive';
@@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [ @@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
74 .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController) 74 .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
75 .controller('DashboardController', DashboardController) 75 .controller('DashboardController', DashboardController)
76 .controller('DashboardSettingsController', DashboardSettingsController) 76 .controller('DashboardSettingsController', DashboardSettingsController)
77 - .controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)  
78 .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController) 77 .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
  78 + .controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
79 .controller('AddWidgetController', AddWidgetController) 79 .controller('AddWidgetController', AddWidgetController)
80 .directive('tbDashboardDetails', DashboardDirective) 80 .directive('tbDashboardDetails', DashboardDirective)
81 .directive('tbEditWidget', EditWidgetDirective) 81 .directive('tbEditWidget', EditWidgetDirective)
  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 +/*@ngInject*/
  17 +export default function ManageAssignedCustomersController($mdDialog, $q, types, dashboardService, actionType, dashboardIds, assignedCustomers) {
  18 +
  19 + var vm = this;
  20 +
  21 + vm.types = types;
  22 + vm.actionType = actionType;
  23 + vm.dashboardIds = dashboardIds;
  24 + vm.assignedCustomers = assignedCustomers;
  25 + if (actionType != 'manage') {
  26 + vm.assignedCustomers = [];
  27 + }
  28 +
  29 + if (actionType == 'manage') {
  30 + vm.titleText = 'dashboard.manage-assigned-customers';
  31 + vm.labelText = 'dashboard.assigned-customers';
  32 + vm.actionName = 'action.update';
  33 + } else if (actionType == 'assign') {
  34 + vm.titleText = 'dashboard.assign-to-customers';
  35 + vm.labelText = 'dashboard.assign-to-customers-text';
  36 + vm.actionName = 'action.assign';
  37 + } else if (actionType == 'unassign') {
  38 + vm.titleText = 'dashboard.unassign-from-customers';
  39 + vm.labelText = 'dashboard.unassign-from-customers-text';
  40 + vm.actionName = 'action.unassign';
  41 + }
  42 +
  43 + vm.submit = submit;
  44 + vm.cancel = cancel;
  45 +
  46 + function cancel () {
  47 + $mdDialog.cancel();
  48 + }
  49 +
  50 + function submit () {
  51 + var tasks = [];
  52 + for (var i=0;i<vm.dashboardIds.length;i++) {
  53 + var dashboardId = vm.dashboardIds[i];
  54 + var promise;
  55 + if (vm.actionType == 'manage') {
  56 + promise = dashboardService.updateDashboardCustomers(dashboardId, vm.assignedCustomers);
  57 + } else if (vm.actionType == 'assign') {
  58 + promise = dashboardService.addDashboardCustomers(dashboardId, vm.assignedCustomers);
  59 + } else if (vm.actionType == 'unassign') {
  60 + promise = dashboardService.removeDashboardCustomers(dashboardId, vm.assignedCustomers);
  61 + }
  62 + tasks.push(promise);
  63 + }
  64 + $q.all(tasks).then(function () {
  65 + $mdDialog.hide();
  66 + });
  67 + }
  68 +
  69 +}
ui/src/app/dashboard/manage-assigned-customers.tpl.html renamed from ui/src/app/dashboard/assign-to-customer.tpl.html
@@ -15,11 +15,11 @@ @@ -15,11 +15,11 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<md-dialog aria-label="{{ 'dashboard.assign-dashboard-to-customer' | translate }}">  
19 - <form name="theForm" ng-submit="vm.assign()"> 18 +<md-dialog aria-label="{{ vm.titleText | translate }}" style="width: 600px;">
  19 + <form name="theForm" ng-submit="vm.submit()">
20 <md-toolbar> 20 <md-toolbar>
21 <div class="md-toolbar-tools"> 21 <div class="md-toolbar-tools">
22 - <h2 translate>dashboard.assign-dashboard-to-customer</h2> 22 + <h2 translate>{{vm.titleText}}</h2>
23 <span flex></span> 23 <span flex></span>
24 <md-button class="md-icon-button" ng-click="vm.cancel()"> 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
@@ -31,42 +31,17 @@ @@ -31,42 +31,17 @@
31 <md-dialog-content> 31 <md-dialog-content>
32 <div class="md-dialog-content"> 32 <div class="md-dialog-content">
33 <fieldset> 33 <fieldset>
34 - <span translate>dashboard.assign-to-customer-text</span>  
35 - <md-input-container class="md-block" style='margin-bottom: 0px;'>  
36 - <label>&nbsp;</label>  
37 - <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">  
38 - search  
39 - </md-icon>  
40 - <input id="customer-search" autofocus ng-model="vm.searchText"  
41 - ng-change="vm.searchCustomerTextUpdated()"  
42 - placeholder="{{ 'common.enter-search' | translate }}"/>  
43 - </md-input-container>  
44 - <div style='min-height: 150px;'>  
45 - <span translate layout-align="center center"  
46 - style="text-transform: uppercase; display: flex; height: 150px;"  
47 - class="md-subhead"  
48 - ng-show="vm.noData()">customer.no-customers-text</span>  
49 - <md-virtual-repeat-container ng-show="vm.hasData()"  
50 - tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex  
51 - style='min-height: 150px; width: 100%;'>  
52 - <md-list>  
53 - <md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand  
54 - class="repeated-item" flex>  
55 - <md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"  
56 - aria-label="{{ 'item.selected' | translate }}"  
57 - ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>  
58 - <span> {{ customer.title }} </span>  
59 - </md-list-item>  
60 - </md-list>  
61 - </md-virtual-repeat-container>  
62 - </div> 34 + <span translate>{{vm.labelText}}</span>
  35 + <tb-entity-list ng-disabled="$root.loading"
  36 + ng-model="vm.assignedCustomers"
  37 + entity-type="vm.types.entityType.customer"></tb-entity-list>
63 </fieldset> 38 </fieldset>
64 </div> 39 </div>
65 </md-dialog-content> 40 </md-dialog-content>
66 <md-dialog-actions layout="row"> 41 <md-dialog-actions layout="row">
67 <span flex></span> 42 <span flex></span>
68 - <md-button ng-disabled="$root.loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">  
69 - {{ 'action.assign' | translate }} 43 + <md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
  44 + {{ vm.actionName | translate }}
70 </md-button> 45 </md-button>
71 <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | 46 <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
72 translate }} 47 translate }}
@@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter @@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
38 if (scope.excludeEntityIds && scope.excludeEntityIds.length) { 38 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
39 limit += scope.excludeEntityIds.length; 39 limit += scope.excludeEntityIds.length;
40 } 40 }
41 - entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) { 41 + var targetType = scope.entityType;
  42 + if (targetType == types.aliasEntityType.current_customer) {
  43 + targetType = types.entityType.customer;
  44 + }
  45 +
  46 + entityService.getEntitiesByNameFilter(targetType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) {
42 if (result) { 47 if (result) {
43 if (scope.excludeEntityIds && scope.excludeEntityIds.length) { 48 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
44 var entities = []; 49 var entities = [];
@@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter @@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
71 76
72 ngModelCtrl.$render = function () { 77 ngModelCtrl.$render = function () {
73 if (ngModelCtrl.$viewValue) { 78 if (ngModelCtrl.$viewValue) {
74 - entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then( 79 + var targetType = scope.entityType;
  80 + if (targetType == types.aliasEntityType.current_customer) {
  81 + targetType = types.entityType.customer;
  82 + }
  83 + entityService.getEntity(targetType, ngModelCtrl.$viewValue).then(
75 function success(entity) { 84 function success(entity) {
76 scope.entity = entity; 85 scope.entity = entity;
77 }, 86 },
@@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter @@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
114 scope.selectEntityText = 'asset.select-asset'; 123 scope.selectEntityText = 'asset.select-asset';
115 scope.entityText = 'asset.asset'; 124 scope.entityText = 'asset.asset';
116 scope.noEntitiesMatchingText = 'asset.no-assets-matching'; 125 scope.noEntitiesMatchingText = 'asset.no-assets-matching';
117 - scope.entityRequiredText = 'asset.asset-required' 126 + scope.entityRequiredText = 'asset.asset-required';
118 break; 127 break;
119 case types.entityType.device: 128 case types.entityType.device:
120 scope.selectEntityText = 'device.select-device'; 129 scope.selectEntityText = 'device.select-device';
121 scope.entityText = 'device.device'; 130 scope.entityText = 'device.device';
122 scope.noEntitiesMatchingText = 'device.no-devices-matching'; 131 scope.noEntitiesMatchingText = 'device.no-devices-matching';
123 - scope.entityRequiredText = 'device.device-required' 132 + scope.entityRequiredText = 'device.device-required';
124 break; 133 break;
125 case types.entityType.rule: 134 case types.entityType.rule:
126 scope.selectEntityText = 'rule.select-rule'; 135 scope.selectEntityText = 'rule.select-rule';
127 scope.entityText = 'rule.rule'; 136 scope.entityText = 'rule.rule';
128 scope.noEntitiesMatchingText = 'rule.no-rules-matching'; 137 scope.noEntitiesMatchingText = 'rule.no-rules-matching';
129 - scope.entityRequiredText = 'rule.rule-required' 138 + scope.entityRequiredText = 'rule.rule-required';
130 break; 139 break;
131 case types.entityType.plugin: 140 case types.entityType.plugin:
132 scope.selectEntityText = 'plugin.select-plugin'; 141 scope.selectEntityText = 'plugin.select-plugin';
133 scope.entityText = 'plugin.plugin'; 142 scope.entityText = 'plugin.plugin';
134 scope.noEntitiesMatchingText = 'plugin.no-plugins-matching'; 143 scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
135 - scope.entityRequiredText = 'plugin.plugin-required' 144 + scope.entityRequiredText = 'plugin.plugin-required';
136 break; 145 break;
137 case types.entityType.tenant: 146 case types.entityType.tenant:
138 scope.selectEntityText = 'tenant.select-tenant'; 147 scope.selectEntityText = 'tenant.select-tenant';
139 scope.entityText = 'tenant.tenant'; 148 scope.entityText = 'tenant.tenant';
140 scope.noEntitiesMatchingText = 'tenant.no-tenants-matching'; 149 scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
141 - scope.entityRequiredText = 'tenant.tenant-required' 150 + scope.entityRequiredText = 'tenant.tenant-required';
142 break; 151 break;
143 case types.entityType.customer: 152 case types.entityType.customer:
144 scope.selectEntityText = 'customer.select-customer'; 153 scope.selectEntityText = 'customer.select-customer';
145 scope.entityText = 'customer.customer'; 154 scope.entityText = 'customer.customer';
146 scope.noEntitiesMatchingText = 'customer.no-customers-matching'; 155 scope.noEntitiesMatchingText = 'customer.no-customers-matching';
147 - scope.entityRequiredText = 'customer.customer-required' 156 + scope.entityRequiredText = 'customer.customer-required';
148 break; 157 break;
149 case types.entityType.user: 158 case types.entityType.user:
150 scope.selectEntityText = 'user.select-user'; 159 scope.selectEntityText = 'user.select-user';
151 scope.entityText = 'user.user'; 160 scope.entityText = 'user.user';
152 scope.noEntitiesMatchingText = 'user.no-users-matching'; 161 scope.noEntitiesMatchingText = 'user.no-users-matching';
153 - scope.entityRequiredText = 'user.user-required' 162 + scope.entityRequiredText = 'user.user-required';
154 break; 163 break;
155 case types.entityType.dashboard: 164 case types.entityType.dashboard:
156 scope.selectEntityText = 'dashboard.select-dashboard'; 165 scope.selectEntityText = 'dashboard.select-dashboard';
157 scope.entityText = 'dashboard.dashboard'; 166 scope.entityText = 'dashboard.dashboard';
158 scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching'; 167 scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
159 - scope.entityRequiredText = 'dashboard.dashboard-required' 168 + scope.entityRequiredText = 'dashboard.dashboard-required';
160 break; 169 break;
161 case types.entityType.alarm: 170 case types.entityType.alarm:
162 scope.selectEntityText = 'alarm.select-alarm'; 171 scope.selectEntityText = 'alarm.select-alarm';
163 scope.entityText = 'alarm.alarm'; 172 scope.entityText = 'alarm.alarm';
164 scope.noEntitiesMatchingText = 'alarm.no-alarms-matching'; 173 scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
165 - scope.entityRequiredText = 'alarm.alarm-required' 174 + scope.entityRequiredText = 'alarm.alarm-required';
  175 + break;
  176 + case types.aliasEntityType.current_customer:
  177 + scope.selectEntityText = 'customer.select-default-customer';
  178 + scope.entityText = 'customer.default-customer';
  179 + scope.noEntitiesMatchingText = 'customer.no-customers-matching';
  180 + scope.entityRequiredText = 'customer.default-customer-required';
166 break; 181 break;
167 } 182 }
168 if (scope.entity && scope.entity.id.entityType != scope.entityType) { 183 if (scope.entity && scope.entity.id.entityType != scope.entityType) {
@@ -32,6 +32,7 @@ @@ -32,6 +32,7 @@
32 <tb-entity-select flex 32 <tb-entity-select flex
33 the-form="theForm" 33 the-form="theForm"
34 tb-required="true" 34 tb-required="true"
  35 + use-alias-entity-types="true"
35 ng-model="filter.singleEntity"> 36 ng-model="filter.singleEntity">
36 </tb-entity-select> 37 </tb-entity-select>
37 </section> 38 </section>
@@ -78,6 +79,7 @@ @@ -78,6 +79,7 @@
78 <tb-entity-select flex 79 <tb-entity-select flex
79 the-form="theForm" 80 the-form="theForm"
80 tb-required="false" 81 tb-required="false"
  82 + use-alias-entity-types="true"
81 ng-model="filter.defaultStateEntity"> 83 ng-model="filter.defaultStateEntity">
82 </tb-entity-select> 84 </tb-entity-select>
83 </div> 85 </div>
@@ -123,6 +125,7 @@ @@ -123,6 +125,7 @@
123 the-form="theForm" 125 the-form="theForm"
124 tb-required="!filter.rootStateEntity" 126 tb-required="!filter.rootStateEntity"
125 ng-disabled="filter.rootStateEntity" 127 ng-disabled="filter.rootStateEntity"
  128 + use-alias-entity-types="true"
126 ng-model="filter.rootEntity"> 129 ng-model="filter.rootEntity">
127 </tb-entity-select> 130 </tb-entity-select>
128 </div> 131 </div>
@@ -139,6 +142,7 @@ @@ -139,6 +142,7 @@
139 <tb-entity-select flex 142 <tb-entity-select flex
140 the-form="theForm" 143 the-form="theForm"
141 tb-required="false" 144 tb-required="false"
  145 + use-alias-entity-types="true"
142 ng-model="filter.defaultStateEntity"> 146 ng-model="filter.defaultStateEntity">
143 </tb-entity-select> 147 </tb-entity-select>
144 </div> 148 </div>
@@ -182,6 +186,7 @@ @@ -182,6 +186,7 @@
182 the-form="theForm" 186 the-form="theForm"
183 tb-required="!filter.rootStateEntity" 187 tb-required="!filter.rootStateEntity"
184 ng-disabled="filter.rootStateEntity" 188 ng-disabled="filter.rootStateEntity"
  189 + use-alias-entity-types="true"
185 ng-model="filter.rootEntity"> 190 ng-model="filter.rootEntity">
186 </tb-entity-select> 191 </tb-entity-select>
187 </div> 192 </div>
@@ -198,6 +203,7 @@ @@ -198,6 +203,7 @@
198 <tb-entity-select flex 203 <tb-entity-select flex
199 the-form="theForm" 204 the-form="theForm"
200 tb-required="false" 205 tb-required="false"
  206 + use-alias-entity-types="true"
201 ng-model="filter.defaultStateEntity"> 207 ng-model="filter.defaultStateEntity">
202 </tb-entity-select> 208 </tb-entity-select>
203 </div> 209 </div>
@@ -249,6 +255,7 @@ @@ -249,6 +255,7 @@
249 the-form="theForm" 255 the-form="theForm"
250 tb-required="!filter.rootStateEntity" 256 tb-required="!filter.rootStateEntity"
251 ng-disabled="filter.rootStateEntity" 257 ng-disabled="filter.rootStateEntity"
  258 + use-alias-entity-types="true"
252 ng-model="filter.rootEntity"> 259 ng-model="filter.rootEntity">
253 </tb-entity-select> 260 </tb-entity-select>
254 </div> 261 </div>
@@ -265,6 +272,7 @@ @@ -265,6 +272,7 @@
265 <tb-entity-select flex 272 <tb-entity-select flex
266 the-form="theForm" 273 the-form="theForm"
267 tb-required="false" 274 tb-required="false"
  275 + use-alias-entity-types="true"
268 ng-model="filter.defaultStateEntity"> 276 ng-model="filter.defaultStateEntity">
269 </tb-entity-select> 277 </tb-entity-select>
270 </div> 278 </div>
@@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) { @@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) {
105 scope: { 105 scope: {
106 theForm: '=?', 106 theForm: '=?',
107 tbRequired: '=?', 107 tbRequired: '=?',
108 - disabled:'=ngDisabled' 108 + disabled:'=ngDisabled',
  109 + useAliasEntityTypes: "=?"
109 } 110 }
110 }; 111 };
111 } 112 }
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 the-form="theForm" 20 the-form="theForm"
21 ng-disabled="disabled" 21 ng-disabled="disabled"
22 tb-required="tbRequired" 22 tb-required="tbRequired"
  23 + use-alias-entity-types="useAliasEntityTypes"
23 ng-model="model.entityType"> 24 ng-model="model.entityType">
24 </tb-entity-type-select> 25 </tb-entity-type-select>
25 <tb-entity-autocomplete flex ng-if="model.entityType" 26 <tb-entity-autocomplete flex ng-if="model.entityType"
@@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity @@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
39 39
40 scope.ngModelCtrl = ngModelCtrl; 40 scope.ngModelCtrl = ngModelCtrl;
41 41
42 - scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes); 42 + scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
43 43
44 scope.typeName = function(type) { 44 scope.typeName = function(type) {
45 return type ? types.entityTypeTranslations[type].type : ''; 45 return type ? types.entityTypeTranslations[type].type : '';
@@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity @@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
79 theForm: '=?', 79 theForm: '=?',
80 tbRequired: '=?', 80 tbRequired: '=?',
81 disabled:'=ngDisabled', 81 disabled:'=ngDisabled',
82 - allowedEntityTypes: "=?" 82 + allowedEntityTypes: "=?",
  83 + useAliasEntityTypes: "=?"
83 } 84 }
84 }; 85 };
85 } 86 }
@@ -540,7 +540,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, @@ -540,7 +540,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
540 function success(dashboard) { 540 function success(dashboard) {
541 var name = dashboard.title; 541 var name = dashboard.title;
542 name = name.toLowerCase().replace(/\W/g,"_"); 542 name = name.toLowerCase().replace(/\W/g,"_");
543 - exportToPc(prepareExport(dashboard), name + '.json'); 543 + exportToPc(prepareDashboardExport(dashboard), name + '.json');
544 }, 544 },
545 function fail(rejection) { 545 function fail(rejection) {
546 var message = rejection; 546 var message = rejection;
@@ -552,6 +552,15 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, @@ -552,6 +552,15 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
552 ); 552 );
553 } 553 }
554 554
  555 + function prepareDashboardExport(dashboard) {
  556 + dashboard = prepareExport(dashboard);
  557 + delete dashboard.assignedCustomers;
  558 + delete dashboard.publicCustomerId;
  559 + delete dashboard.assignedCustomersText;
  560 + delete dashboard.assignedCustomersIds;
  561 + return dashboard;
  562 + }
  563 +
555 function importDashboard($event) { 564 function importDashboard($event) {
556 var deferred = $q.defer(); 565 var deferred = $q.defer();
557 openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then( 566 openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(
@@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', []) @@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', [])
383 "idCopiedMessage": "Customer Id has been copied to clipboard", 383 "idCopiedMessage": "Customer Id has been copied to clipboard",
384 "select-customer": "Select customer", 384 "select-customer": "Select customer",
385 "no-customers-matching": "No customers matching '{{entity}}' were found.", 385 "no-customers-matching": "No customers matching '{{entity}}' were found.",
386 - "customer-required": "Customer is required" 386 + "customer-required": "Customer is required",
  387 + "select-default-customer": "Select default customer",
  388 + "default-customer": "Default customer",
  389 + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
387 }, 390 },
388 "datetime": { 391 "datetime": {
389 "date-from": "Date from", 392 "date-from": "Date from",
@@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', []) @@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
404 "unassign-from-customer": "Unassign from customer", 407 "unassign-from-customer": "Unassign from customer",
405 "make-public": "Make dashboard public", 408 "make-public": "Make dashboard public",
406 "make-private": "Make dashboard private", 409 "make-private": "Make dashboard private",
  410 + "manage-assigned-customers": "Manage assigned customers",
  411 + "assigned-customers": "Assigned customers",
  412 + "assign-to-customers": "Assign Dashboard(s) To Customers",
  413 + "assign-to-customers-text": "Please select the customers to assign the dashboard(s)",
  414 + "unassign-from-customers": "Unassign Dashboard(s) From Customers",
  415 + "unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)",
407 "no-dashboards-text": "No dashboards found", 416 "no-dashboards-text": "No dashboards found",
408 "no-widgets": "No widgets configured", 417 "no-widgets": "No widgets configured",
409 "add-widget": "Add new widget", 418 "add-widget": "Add new widget",
@@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', []) @@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
418 "add-dashboard-text": "Add new dashboard", 427 "add-dashboard-text": "Add new dashboard",
419 "assign-dashboards": "Assign dashboards", 428 "assign-dashboards": "Assign dashboards",
420 "assign-new-dashboard": "Assign new dashboard", 429 "assign-new-dashboard": "Assign new dashboard",
421 - "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer", 430 + "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customers",
  431 + "unassign-dashboards-action-text": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customers",
422 "delete-dashboards": "Delete dashboards", 432 "delete-dashboards": "Delete dashboards",
423 "unassign-dashboards": "Unassign dashboards", 433 "unassign-dashboards": "Unassign dashboards",
424 "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer", 434 "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
@@ -500,6 +510,7 @@ export default angular.module('thingsboard.locale', []) @@ -500,6 +510,7 @@ export default angular.module('thingsboard.locale', [])
500 "Please contact your administrator in order to resolve this issue.", 510 "Please contact your administrator in order to resolve this issue.",
501 "select-devices": "Select devices", 511 "select-devices": "Select devices",
502 "assignedToCustomer": "Assigned to customer", 512 "assignedToCustomer": "Assigned to customer",
  513 + "assignedToCustomers": "Assigned to customers",
503 "public": "Public", 514 "public": "Public",
504 "public-link": "Public link", 515 "public-link": "Public link",
505 "copy-public-link": "Copy public link", 516 "copy-public-link": "Copy public link",
@@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', []) @@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
735 "type-alarms": "Alarms", 746 "type-alarms": "Alarms",
736 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }", 747 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
737 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", 748 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  749 + "type-current-customer": "Current Customer",
738 "search": "Search entities", 750 "search": "Search entities",
739 "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected", 751 "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
740 "entity-name": "Entity name", 752 "entity-name": "Entity name",