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 434 try {
435 435 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
436 436 DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
437   - SecurityUser authUser = getCurrentUser();
438 437 checkDashboard(dashboardInfo);
439 438 return dashboardInfo;
440 439 } catch (Exception e) {
... ... @@ -447,7 +446,7 @@ public abstract class BaseController {
447 446 checkTenantId(dashboard.getTenantId());
448 447 SecurityUser authUser = getCurrentUser();
449 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 450 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
452 451 ThingsboardErrorCode.PERMISSION_DENIED);
453 452 }
... ...
... ... @@ -18,10 +18,7 @@ package org.thingsboard.server.controller;
18 18 import org.springframework.http.HttpStatus;
19 19 import org.springframework.security.access.prepost.PreAuthorize;
20 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 22 import org.thingsboard.server.common.data.audit.ActionType;
26 23 import org.thingsboard.server.common.data.id.CustomerId;
27 24 import org.thingsboard.server.common.data.id.DashboardId;
... ... @@ -34,6 +31,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
34 31 import org.thingsboard.server.dao.model.ModelConstants;
35 32 import org.thingsboard.server.exception.ThingsboardException;
36 33
  34 +import java.util.HashSet;
  35 +import java.util.Set;
  36 +
37 37 @RestController
38 38 @RequestMapping("/api")
39 39 public class DashboardController extends BaseController {
... ... @@ -182,6 +182,158 @@ public class DashboardController extends BaseController {
182 182 }
183 183
184 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 337 @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
186 338 @ResponseBody
187 339 public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
... ...
... ... @@ -15,12 +15,13 @@
15 15 */
16 16 package org.thingsboard.server.service.install;
17 17
  18 +import com.fasterxml.jackson.databind.JavaType;
18 19 import com.fasterxml.jackson.databind.ObjectMapper;
19 20 import lombok.extern.slf4j.Slf4j;
20 21 import org.apache.commons.csv.CSVFormat;
21 22 import org.apache.commons.csv.CSVParser;
22 23 import org.apache.commons.lang3.StringUtils;
23   -import com.fasterxml.jackson.databind.JsonNode;
  24 +import org.thingsboard.server.common.data.ShortCustomerInfo;
24 25 import org.thingsboard.server.common.data.UUIDConverter;
25 26 import org.thingsboard.server.common.data.id.CustomerId;
26 27 import org.thingsboard.server.common.data.id.DashboardId;
... ... @@ -54,7 +55,8 @@ public class DatabaseHelper {
54 55 public static final ObjectMapper objectMapper = new ObjectMapper();
55 56
56 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 60 try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) {
59 61 csvParser.forEach(record -> {
60 62 String customerIdString = record.get(CUSTOMER_ID);
... ... @@ -63,12 +65,11 @@ public class DatabaseHelper {
63 65 List<CustomerId> customerIds = new ArrayList<>();
64 66 if (!StringUtils.isEmpty(assignedCustomersString)) {
65 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 71 if (!customerId.isNullUid()) {
71   - customerIds.add(new CustomerId(UUID.fromString(strCustomerId)));
  72 + customerIds.add(customerId);
72 73 }
73 74 });
74 75 } catch (IOException e) {
... ... @@ -78,7 +79,7 @@ public class DatabaseHelper {
78 79 if (!StringUtils.isEmpty(customerIdString)) {
79 80 CustomerId customerId = new CustomerId(toUUID(customerIdString, sql));
80 81 if (!customerId.isNullUid()) {
81   - customerIds.add(new CustomerId(toUUID(customerIdString, sql)));
  82 + customerIds.add(customerId);
82 83 }
83 84 }
84 85 for (CustomerId customerId : customerIds) {
... ...
... ... @@ -138,10 +138,10 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
138 138 Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
139 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 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 146 Dashboard unassignedDashboard =
147 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 69 return false;
70 70 }
71 71
  72 + @JsonIgnore
  73 + public ShortCustomerInfo toShortCustomerInfo() {
  74 + return new ShortCustomerInfo(id, title, isPublic());
  75 + }
  76 +
72 77 @Override
73 78 @JsonProperty(access = Access.READ_ONLY)
74 79 public String getName() {
... ...
... ... @@ -20,16 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
20 20 import org.thingsboard.server.common.data.id.DashboardId;
21 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 25 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
29 26
30 27 private TenantId tenantId;
31 28 private String title;
32   - private Map<String, String> assignedCustomers;
  29 + private Set<ShortCustomerInfo> assignedCustomers;
33 30
34 31 public DashboardInfo() {
35 32 super();
... ... @@ -62,38 +59,56 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
62 59 this.title = title;
63 60 }
64 61
65   - public Map<String, String> getAssignedCustomers() {
  62 + public Set<ShortCustomerInfo> getAssignedCustomers() {
66 63 return assignedCustomers;
67 64 }
68 65
69   - public void setAssignedCustomers(Map<String, String> assignedCustomers) {
  66 + public void setAssignedCustomers(Set<ShortCustomerInfo> assignedCustomers) {
70 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 88 return false;
76 89 } else {
77 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 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 102 return true;
89 103 } else {
90 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 112 return true;
98 113 } else {
99 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 98 log.trace("Executing saveCustomer [{}]", customer);
99 99 customerValidator.validate(customer);
100 100 Customer savedCustomer = customerDao.save(customer);
101   - dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId(), savedCustomer.getTitle());
  101 + dashboardService.updateCustomerDashboards(savedCustomer.getId());
102 102 return savedCustomer;
103 103 }
104 104
... ... @@ -110,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
110 110 if (customer == null) {
111 111 throw new IncorrectParameterException("Unable to delete non-existent customer.");
112 112 }
113   - dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
  113 + dashboardService.unassignCustomerDashboards(customerId);
114 114 assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
115 115 deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
116 116 userService.deleteCustomerUsers(customer.getTenantId(), customerId);
... ...
... ... @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
26 26 import org.thingsboard.server.common.data.page.TimePageData;
27 27 import org.thingsboard.server.common.data.page.TimePageLink;
28 28
29   -import java.sql.Time;
  29 +import java.util.Set;
30 30
31 31 public interface DashboardService {
32 32
... ... @@ -52,8 +52,8 @@ public interface DashboardService {
52 52
53 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 117 if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
118 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 121 try {
122 122 createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
123 123 } catch (ExecutionException | InterruptedException e) {
... ... @@ -133,7 +133,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
133 133 @Override
134 134 public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
135 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 141 try {
138 142 deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
139 143 } catch (ExecutionException | InterruptedException e) {
... ... @@ -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 154 Dashboard dashboard = findDashboardById(dashboardId);
151   - if (dashboard.updateAssignedCustomer(customerId, customerTitle)) {
  155 + if (dashboard.updateAssignedCustomer(customer)) {
152 156 return saveDashboard(dashboard);
153 157 } else {
154 158 return dashboard;
... ... @@ -207,20 +211,25 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
207 211 }
208 212
209 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 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 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 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 235 private DataValidator<Dashboard> dashboardValidator =
... ... @@ -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 275 @Override
269   - protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
  276 + protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
270 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 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 281 throw new RuntimeException(e);
275 282 }
276 283 }
277 284
278 285 @Override
279 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 300 @Override
298   - protected List<DashboardInfo> findEntities(CustomerId id, TimePageLink pageLink) {
  301 + protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
299 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 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 306 throw new RuntimeException(e);
304 307 }
305 308 }
306 309
307 310 @Override
308 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 20 import com.datastax.driver.mapping.annotations.PartitionKey;
21 21 import com.datastax.driver.mapping.annotations.Table;
22 22 import com.fasterxml.jackson.core.JsonProcessingException;
  23 +import com.fasterxml.jackson.databind.JavaType;
23 24 import com.fasterxml.jackson.databind.JsonNode;
24 25 import com.fasterxml.jackson.databind.ObjectMapper;
25 26 import lombok.EqualsAndHashCode;
26 27 import lombok.ToString;
27 28 import lombok.extern.slf4j.Slf4j;
  29 +import org.springframework.util.StringUtils;
28 30 import org.thingsboard.server.common.data.Dashboard;
  31 +import org.thingsboard.server.common.data.ShortCustomerInfo;
29 32 import org.thingsboard.server.common.data.id.DashboardId;
30 33 import org.thingsboard.server.common.data.id.TenantId;
31 34 import org.thingsboard.server.dao.model.SearchTextEntity;
32 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 39 import java.util.UUID;
36 40
37 41 import static org.thingsboard.server.dao.model.ModelConstants.*;
... ... @@ -43,6 +47,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
43 47 public final class DashboardEntity implements SearchTextEntity<Dashboard> {
44 48
45 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 53 @PartitionKey(value = 0)
48 54 @Column(name = ID_PROPERTY)
... ... @@ -58,8 +64,8 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
58 64 @Column(name = SEARCH_TEXT_PROPERTY)
59 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 70 @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
65 71 private JsonNode configuration;
... ... @@ -77,7 +83,11 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
77 83 }
78 84 this.title = dashboard.getTitle();
79 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 92 this.configuration = dashboard.getConfiguration();
83 93 }
... ... @@ -106,11 +116,11 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
106 116 this.title = title;
107 117 }
108 118
109   - public JsonNode getAssignedCustomers() {
  119 + public String getAssignedCustomers() {
110 120 return assignedCustomers;
111 121 }
112 122
113   - public void setAssignedCustomers(JsonNode assignedCustomers) {
  123 + public void setAssignedCustomers(String assignedCustomers) {
114 124 this.assignedCustomers = assignedCustomers;
115 125 }
116 126
... ... @@ -144,10 +154,10 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
144 154 dashboard.setTenantId(new TenantId(tenantId));
145 155 }
146 156 dashboard.setTitle(title);
147   - if (assignedCustomers != null) {
  157 + if (!StringUtils.isEmpty(assignedCustomers)) {
148 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 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 import com.datastax.driver.mapping.annotations.PartitionKey;
21 21 import com.datastax.driver.mapping.annotations.Table;
22 22 import com.fasterxml.jackson.core.JsonProcessingException;
23   -import com.fasterxml.jackson.databind.JsonNode;
  23 +import com.fasterxml.jackson.databind.JavaType;
24 24 import com.fasterxml.jackson.databind.ObjectMapper;
25 25 import lombok.EqualsAndHashCode;
26 26 import lombok.ToString;
27 27 import lombok.extern.slf4j.Slf4j;
  28 +import org.springframework.util.StringUtils;
28 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 31 import org.thingsboard.server.common.data.id.DashboardId;
31 32 import org.thingsboard.server.common.data.id.TenantId;
32 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 37 import java.util.UUID;
38 38
39 39 import static org.thingsboard.server.dao.model.ModelConstants.*;
... ... @@ -45,6 +45,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
45 45 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
46 46
47 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 51 @PartitionKey(value = 0)
50 52 @Column(name = ID_PROPERTY)
... ... @@ -60,8 +62,8 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
60 62 @Column(name = SEARCH_TEXT_PROPERTY)
61 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 68 public DashboardInfoEntity() {
67 69 super();
... ... @@ -76,7 +78,11 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
76 78 }
77 79 this.title = dashboardInfo.getTitle();
78 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 110 this.title = title;
105 111 }
106 112
107   - public JsonNode getAssignedCustomers() {
  113 + public String getAssignedCustomers() {
108 114 return assignedCustomers;
109 115 }
110 116
111   - public void setAssignedCustomers(JsonNode assignedCustomers) {
  117 + public void setAssignedCustomers(String assignedCustomers) {
112 118 this.assignedCustomers = assignedCustomers;
113 119 }
114 120
... ... @@ -134,10 +140,10 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
134 140 dashboardInfo.setTenantId(new TenantId(tenantId));
135 141 }
136 142 dashboardInfo.setTitle(title);
137   - if (assignedCustomers != null) {
  143 + if (!StringUtils.isEmpty(assignedCustomers)) {
138 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 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 17
18 18 import com.datastax.driver.core.utils.UUIDs;
19 19 import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.JavaType;
20 21 import com.fasterxml.jackson.databind.JsonNode;
21 22 import com.fasterxml.jackson.databind.ObjectMapper;
22 23 import lombok.Data;
... ... @@ -24,8 +25,9 @@ import lombok.EqualsAndHashCode;
24 25 import lombok.extern.slf4j.Slf4j;
25 26 import org.hibernate.annotations.Type;
26 27 import org.hibernate.annotations.TypeDef;
  28 +import org.springframework.util.StringUtils;
27 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 31 import org.thingsboard.server.common.data.id.DashboardId;
30 32 import org.thingsboard.server.common.data.id.TenantId;
31 33 import org.thingsboard.server.dao.model.BaseSqlEntity;
... ... @@ -36,9 +38,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
36 38 import javax.persistence.Column;
37 39 import javax.persistence.Entity;
38 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 44 @Data
44 45 @Slf4j
... ... @@ -49,6 +50,8 @@ import java.util.Set;
49 50 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
50 51
51 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 56 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
54 57 private String tenantId;
... ... @@ -59,9 +62,8 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
59 62 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
60 63 private String searchText;
61 64
62   - @Type(type = "json")
63 65 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
64   - private JsonNode assignedCustomers;
  66 + private String assignedCustomers;
65 67
66 68 @Type(type = "json")
67 69 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
... ... @@ -80,7 +82,11 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
80 82 }
81 83 this.title = dashboard.getTitle();
82 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 91 this.configuration = dashboard.getConfiguration();
86 92 }
... ... @@ -103,10 +109,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
103 109 dashboard.setTenantId(new TenantId(toUUID(tenantId)));
104 110 }
105 111 dashboard.setTitle(title);
106   - if (assignedCustomers != null) {
  112 + if (!StringUtils.isEmpty(assignedCustomers)) {
107 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 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 17
18 18 import com.datastax.driver.core.utils.UUIDs;
19 19 import com.fasterxml.jackson.core.JsonProcessingException;
20   -import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.JavaType;
21 21 import com.fasterxml.jackson.databind.ObjectMapper;
22 22 import lombok.Data;
23 23 import lombok.EqualsAndHashCode;
24 24 import lombok.extern.slf4j.Slf4j;
25   -import org.hibernate.annotations.Type;
  25 +import org.springframework.util.StringUtils;
26 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 28 import org.thingsboard.server.common.data.id.DashboardId;
29 29 import org.thingsboard.server.common.data.id.TenantId;
30 30 import org.thingsboard.server.dao.model.BaseSqlEntity;
... ... @@ -34,8 +34,8 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
34 34 import javax.persistence.Column;
35 35 import javax.persistence.Entity;
36 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 40 @Data
41 41 @Slf4j
... ... @@ -45,6 +45,8 @@ import java.util.Set;
45 45 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
46 46
47 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 51 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
50 52 private String tenantId;
... ... @@ -55,9 +57,8 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
55 57 @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
56 58 private String searchText;
57 59
58   - @Type(type = "json")
59 60 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
60   - private JsonNode assignedCustomers;
  61 + private String assignedCustomers;
61 62
62 63 public DashboardInfoEntity() {
63 64 super();
... ... @@ -72,7 +73,11 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
72 73 }
73 74 this.title = dashboardInfo.getTitle();
74 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 103 dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
99 104 }
100 105 dashboardInfo.setTitle(title);
101   - if (assignedCustomers != null) {
  106 + if (!StringUtils.isEmpty(assignedCustomers)) {
102 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 110 log.warn("Unable to parse assigned customers!", e);
106 111 }
107 112 }
... ...
... ... @@ -320,7 +320,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
320 320
321 321 Assert.assertEquals(dashboards, loadedDashboards);
322 322
323   - dashboardService.unassignCustomerDashboards(tenantId, customerId);
  323 + dashboardService.unassignCustomerDashboards(customerId);
324 324
325 325 pageLink = new TimePageLink(42);
326 326 pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
... ...
... ... @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', [])
17 17 .factory('dashboardService', DashboardService).name;
18 18
19 19 /*@ngInject*/
20   -function DashboardService($rootScope, $http, $q, $location, customerService) {
  20 +function DashboardService($rootScope, $http, $q, $location, $filter) {
21 21
22 22 var stDiffPromise;
23 23
... ... @@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
37 37 deleteDashboard: deleteDashboard,
38 38 saveDashboard: saveDashboard,
39 39 unassignDashboardFromCustomer: unassignDashboardFromCustomer,
  40 + updateDashboardCustomers: updateDashboardCustomers,
  41 + addDashboardCustomers: addDashboardCustomers,
  42 + removeDashboardCustomers: removeDashboardCustomers,
40 43 makeDashboardPublic: makeDashboardPublic,
  44 + makeDashboardPrivate: makeDashboardPrivate,
41 45 getPublicDashboardLink: getPublicDashboardLink
42 46 }
43 47
... ... @@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
56 60 url += '&textOffset=' + pageLink.textOffset;
57 61 }
58 62 $http.get(url, config).then(function success(response) {
59   - deferred.resolve(response.data);
  63 + deferred.resolve(prepareDashboards(response.data));
60 64 }, function fail() {
61 65 deferred.reject();
62 66 });
63 67 return deferred.promise;
64 68 }
65 69
66   - function getTenantDashboards(pageLink, applyCustomersInfo, config) {
  70 + function getTenantDashboards(pageLink, config) {
67 71 var deferred = $q.defer();
68 72 var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
69 73 if (angular.isDefined(pageLink.textSearch)) {
... ... @@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
76 80 url += '&textOffset=' + pageLink.textOffset;
77 81 }
78 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 84 }, function fail() {
93 85 deferred.reject();
94 86 });
95 87 return deferred.promise;
96 88 }
97 89
98   - function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) {
  90 + function getCustomerDashboards(customerId, pageLink, config) {
99 91 var deferred = $q.defer();
100 92 var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
101   - if (angular.isDefined(pageLink.textSearch)) {
102   - url += '&textSearch=' + pageLink.textSearch;
103   - }
104 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 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 102 }, function fail() {
125 103 deferred.reject();
126 104 });
... ... @@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
151 129 var deferred = $q.defer();
152 130 var url = '/api/dashboard/' + dashboardId;
153 131 $http.get(url, null).then(function success(response) {
154   - deferred.resolve(response.data);
  132 + deferred.resolve(prepareDashboard(response.data));
155 133 }, function fail() {
156 134 deferred.reject();
157 135 });
... ... @@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
162 140 var deferred = $q.defer();
163 141 var url = '/api/dashboard/info/' + dashboardId;
164 142 $http.get(url, config).then(function success(response) {
165   - deferred.resolve(response.data);
  143 + deferred.resolve(prepareDashboard(response.data));
166 144 }, function fail() {
167 145 deferred.reject();
168 146 });
... ... @@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
172 150 function saveDashboard(dashboard) {
173 151 var deferred = $q.defer();
174 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 155 }, function fail() {
178 156 deferred.reject();
179 157 });
... ... @@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
195 173 var deferred = $q.defer();
196 174 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
197 175 $http.post(url, null).then(function success(response) {
198   - deferred.resolve(response.data);
  176 + deferred.resolve(prepareDashboard(response.data));
199 177 }, function fail() {
200 178 deferred.reject();
201 179 });
202 180 return deferred.promise;
203 181 }
204 182
205   - function unassignDashboardFromCustomer(dashboardId) {
  183 + function unassignDashboardFromCustomer(customerId, dashboardId) {
206 184 var deferred = $q.defer();
207   - var url = '/api/customer/dashboard/' + dashboardId;
  185 + var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
208 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 221 }, function fail() {
211 222 deferred.reject();
212 223 });
... ... @@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
217 228 var deferred = $q.defer();
218 229 var url = '/api/customer/public/dashboard/' + dashboardId;
219 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 243 }, function fail() {
222 244 deferred.reject();
223 245 });
... ... @@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
230 252 if (port != 80 && port != 443) {
231 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 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 273 break;
274 274 case types.entityType.dashboard:
275 275 if (user.authority === 'CUSTOMER_USER') {
276   - promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config);
  276 + promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
277 277 } else {
278   - promise = dashboardService.getTenantDashboards(pageLink, false, config);
  278 + promise = dashboardService.getTenantDashboards(pageLink, config);
279 279 }
280 280 break;
281 281 case types.entityType.user:
... ... @@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
403 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 421 function getStateEntityId(filter, stateParams) {
407 422 var entityId = null;
408 423 if (stateParams) {
... ... @@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
417 432 if (!entityId) {
418 433 entityId = filter.defaultStateEntity;
419 434 }
  435 + if (entityId) {
  436 + entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
  437 + }
420 438 return entityId;
421 439 }
422 440
... ... @@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
432 450 var stateEntityId = getStateEntityId(filter, stateParams);
433 451 switch (filter.type) {
434 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 455 function success(entity) {
437 456 result.entities = entitiesToEntitiesInfo([entity]);
438 457 deferred.resolve(result);
... ... @@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
530 549 rootEntityId = filter.rootEntity.id;
531 550 }
532 551 if (rootEntityType && rootEntityId) {
  552 + var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
533 553 var searchQuery = {
534 554 parameters: {
535   - rootId: rootEntityId,
536   - rootType: rootEntityType,
  555 + rootId: relationQueryRootEntityId.id,
  556 + rootType: relationQueryRootEntityId.entityType,
537 557 direction: filter.direction
538 558 },
539 559 filters: filter.filters
... ... @@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
571 591 rootEntityId = filter.rootEntity.id;
572 592 }
573 593 if (rootEntityType && rootEntityId) {
  594 + var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
574 595 searchQuery = {
575 596 parameters: {
576   - rootId: rootEntityId,
577   - rootType: rootEntityType,
  597 + rootId: searchQueryRootEntityId.id,
  598 + rootType: searchQueryRootEntityId.entityType,
578 599 direction: filter.direction
579 600 },
580 601 relationType: filter.relationType
... ... @@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
709 730 return result;
710 731 }
711 732
712   - function prepareAllowedEntityTypesList(allowedEntityTypes) {
  733 + function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
713 734 var authority = userService.getAuthority();
714 735 var entityTypes = {};
715 736 switch(authority) {
... ... @@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
726 747 entityTypes.rule = types.entityType.rule;
727 748 entityTypes.plugin = types.entityType.plugin;
728 749 entityTypes.dashboard = types.entityType.dashboard;
  750 + if (useAliasEntityTypes) {
  751 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  752 + }
729 753 break;
730 754 case 'CUSTOMER_USER':
731 755 entityTypes.device = types.entityType.device;
732 756 entityTypes.asset = types.entityType.asset;
733 757 entityTypes.customer = types.entityType.customer;
734 758 entityTypes.dashboard = types.entityType.dashboard;
  759 + if (useAliasEntityTypes) {
  760 + entityTypes.current_customer = types.aliasEntityType.current_customer;
  761 + }
735 762 break;
736 763 }
737 764
... ...
... ... @@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
266 266 var pageLink = {limit: 100};
267 267 var fetchDashboardsPromise;
268 268 if (currentUser.authority === 'TENANT_ADMIN') {
269   - fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false);
  269 + fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
270 270 } else {
271   - fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false);
  271 + fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
272 272 }
273 273 fetchDashboardsPromise.then(
274 274 function success(result) {
... ...
... ... @@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', [])
296 296 dashboard: "DASHBOARD",
297 297 alarm: "ALARM"
298 298 },
  299 + aliasEntityType: {
  300 + current_customer: "CURRENT_CUSTOMER"
  301 + },
299 302 entityTypeTranslations: {
300 303 "DEVICE": {
301 304 type: 'entity.type-device',
... ... @@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', [])
350 353 typePlural: 'entity.type-alarms',
351 354 list: 'entity.list-of-alarms',
352 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 362 entitySearchDirection: {
... ...
... ... @@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
48 48 var promise;
49 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 50 if (scope.customerId) {
51   - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true});
  51 + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
52 52 } else {
53 53 promise = $q.when({data: []});
54 54 }
... ... @@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
60 60 promise = $q.when({data: []});
61 61 }
62 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 48 var promise;
49 49 if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
50 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 52 } else {
53 53 promise = $q.when({data: []});
54 54 }
55 55 } else {
56   - promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true});
  56 + promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
57 57 }
58 58
59 59 promise.then(function success(result) {
... ...
... ... @@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD
52 52 fetchMoreItems_: function () {
53 53 if (vm.dashboards.hasNext && !vm.dashboards.pending) {
54 54 vm.dashboards.pending = true;
55   - dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then(
  55 + dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
56 56 function success(dashboards) {
57 57 vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
58 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 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 19 ng-show="!isEdit && dashboardScope === 'tenant'"
20 20 class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
21 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 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 34 <md-button ng-click="onDeleteDashboard({event: $event})"
31 35 ng-show="!isEdit && dashboardScope === 'tenant'"
32 36 class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
33 37 <md-content class="md-padding" layout="column">
34 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 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 45 <tb-social-share-panel style="padding-bottom: 10px;"
41 46 share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
42 47 share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
... ...
... ... @@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 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 24 var linker = function (scope, element) {
25 25 var template = $templateCache.get(dashboardFieldsetTemplate);
26 26 element.html(template);
27   -
28   - scope.isAssignedToCustomer = false;
29   - scope.isPublic = false;
30   - scope.assignedCustomer = null;
31 27 scope.publicLink = null;
32   -
33 28 scope.$watch('dashboard', function(newVal) {
34 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 32 } else {
49   - scope.isAssignedToCustomer = false;
50   - scope.isPublic = false;
51 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 47 scope: {
67 48 dashboard: '=',
68 49 isEdit: '=',
  50 + customerId: '=',
69 51 dashboardScope: '=',
70 52 theForm: '=',
71   - onAssignToCustomer: '&',
72 53 onMakePublic: '&',
  54 + onMakePrivate: '&',
  55 + onManageAssignedCustomers: '&',
73 56 onUnassignFromCustomer: '&',
74 57 onExportDashboard: '&',
75 58 onDeleteDashboard: '&'
... ...
... ... @@ -17,12 +17,14 @@
17 17
18 18 import addDashboardTemplate from './add-dashboard.tpl.html';
19 19 import dashboardCard from './dashboard-card.tpl.html';
20   -import assignToCustomerTemplate from './assign-to-customer.tpl.html';
21 20 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
22 21 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html';
  22 +import manageAssignedCustomersTemplate from './manage-assigned-customers.tpl.html';
23 23
24 24 /* eslint-enable import/no-unresolved, import/default */
25 25
  26 +import './dashboard-card.scss';
  27 +
26 28 /*@ngInject*/
27 29 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) {
28 30
... ... @@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast
48 50 export function DashboardCardController(types) {
49 51
50 52 var vm = this;
51   -
52 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 57 /*@ngInject*/
... ... @@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ
135 122
136 123 vm.dashboardsScope = $state.$current.data.dashboardsType;
137 124
138   - vm.assignToCustomer = assignToCustomer;
139 125 vm.makePublic = makePublic;
  126 + vm.makePrivate = makePrivate;
  127 + vm.manageAssignedCustomers = manageAssignedCustomers;
140 128 vm.unassignFromCustomer = unassignFromCustomer;
141 129 vm.exportDashboard = exportDashboard;
142 130
... ... @@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ
155 143 }
156 144
157 145 if (customerId) {
  146 + vm.customerId = customerId;
158 147 vm.customerDashboardsTitle = $translate.instant('customer.dashboards');
159 148 customerService.getShortCustomerInfo(customerId).then(
160 149 function success(info) {
... ... @@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ
167 156
168 157 if (vm.dashboardsScope === 'tenant') {
169 158 fetchDashboardsFunction = function (pageLink) {
170   - return dashboardService.getTenantDashboards(pageLink, true);
  159 + return dashboardService.getTenantDashboards(pageLink);
171 160 };
172 161 deleteDashboardFunction = function (dashboardId) {
173 162 return dashboardService.deleteDashboard(dashboardId);
... ... @@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ
194 183 details: function() { return $translate.instant('dashboard.make-public') },
195 184 icon: "share",
196 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 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 213 onAction: function ($event, item) {
203 214 assignToCustomer($event, [ item.id.id ]);
204 215 },
... ... @@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ
208 219 isEnabled: function(dashboard) {
209 220 return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
210 221 }
211   - });
212   - dashboardActionsList.push({
  222 + });*/
  223 + /*dashboardActionsList.push({
213 224 onAction: function ($event, item) {
214 225 unassignFromCustomer($event, item, false);
215 226 },
... ... @@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ
219 230 isEnabled: function(dashboard) {
220 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 235 dashboardActionsList.push(
236 236 {
... ... @@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ
246 246 dashboardGroupActionsList.push(
247 247 {
248 248 onAction: function ($event, items) {
249   - assignDashboardsToCustomer($event, items);
  249 + assignDashboardsToCustomers($event, items);
250 250 },
251 251 name: function() { return $translate.instant('dashboard.assign-dashboards') },
252 252 details: function(selectedCount) {
... ... @@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ
255 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 270 dashboardGroupActionsList.push(
260 271 {
... ... @@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ
290 301 });
291 302 } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
292 303 fetchDashboardsFunction = function (pageLink) {
293   - return dashboardService.getCustomerDashboards(customerId, pageLink, true);
  304 + return dashboardService.getCustomerDashboards(customerId, pageLink);
294 305 };
295 306 deleteDashboardFunction = function (dashboardId) {
296   - return dashboardService.unassignDashboardFromCustomer(dashboardId);
  307 + return dashboardService.unassignDashboardFromCustomer(customerId, dashboardId);
297 308 };
298 309 refreshDashboardsParamsFunction = function () {
299 310 return {"customerId": customerId, "topIndex": vm.topIndex};
... ... @@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ
314 325 dashboardActionsList.push(
315 326 {
316 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 333 isEnabled: function(dashboard) {
323   - return dashboard && !dashboard.assignedCustomer.isPublic;
  334 + return dashboard && customerId == dashboard.publicCustomerId;
324 335 }
325 336 }
326 337 );
  338 +
327 339 dashboardActionsList.push(
328 340 {
329 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 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 353 dashboardGroupActionsList.push(
342 354 {
343 355 onAction: function ($event, items) {
344   - unassignDashboardsFromCustomer($event, items);
  356 + unassignDashboardsFromCustomer($event, items, customerId);
345 357 },
346 358 name: function() { return $translate.instant('dashboard.unassign-dashboards') },
347 359 details: function(selectedCount) {
... ... @@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ
351 363 }
352 364 );
353 365
354   -
355 366 vm.dashboardGridConfig.addItemAction = {
356 367 onAction: function ($event) {
357 368 addDashboardsToCustomer($event);
... ... @@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ
428 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 463 if ($event) {
433 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 480 function addDashboardsToCustomer($event) {
... ... @@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ
468 482 $event.stopPropagation();
469 483 }
470 484 var pageSize = 10;
471   - dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}, false).then(
  485 + dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
472 486 function success(_dashboards) {
473 487 var dashboards = {
474 488 pageSize: pageSize,
... ... @@ -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 517 if ($event) {
512 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 523 var confirm = $mdDialog.confirm()
527 524 .targetEvent($event)
528 525 .title(title)
... ... @@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ
531 528 .cancel($translate.instant('action.no'))
532 529 .ok($translate.instant('action.yes'));
533 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 532 vm.grid.refreshList();
536 533 });
537 534 });
... ... @@ -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 577 function exportDashboard($event, dashboard) {
560 578 $event.stopPropagation();
561 579 importExport.exportDashboard(dashboard.id.id);
562 580 }
563 581
564   - function unassignDashboardsFromCustomer($event, items) {
  582 + function unassignDashboardsFromCustomer($event, items, customerId) {
565 583 var confirm = $mdDialog.confirm()
566 584 .targetEvent($event)
567 585 .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
... ... @@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ
572 590 $mdDialog.show(confirm).then(function () {
573 591 var tasks = [];
574 592 for (var id in items.selections) {
575   - tasks.push(dashboardService.unassignDashboardFromCustomer(id));
  593 + tasks.push(dashboardService.unassignDashboardFromCustomer(customerId, id));
576 594 }
577 595 $q.all(tasks).then(function () {
578 596 vm.grid.refreshList();
... ...
... ... @@ -25,10 +25,12 @@
25 25 <tb-dashboard-details dashboard="vm.grid.operatingItem()"
26 26 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
27 27 dashboard-scope="vm.dashboardsScope"
  28 + customer-id="vm.customerId"
28 29 the-form="vm.grid.detailsForm"
29   - on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
30 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 34 on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
33 35 on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
34 36 </md-tab>
... ...
... ... @@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes';
40 40 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
41 41 import DashboardController from './dashboard.controller';
42 42 import DashboardSettingsController from './dashboard-settings.controller';
43   -import AssignDashboardToCustomerController from './assign-to-customer.controller';
44 43 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
  44 +import ManageAssignedCustomersController from './manage-assigned-customers.controller';
45 45 import AddWidgetController from './add-widget.controller';
46 46 import DashboardDirective from './dashboard.directive';
47 47 import EditWidgetDirective from './edit-widget.directive';
... ... @@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
74 74 .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
75 75 .controller('DashboardController', DashboardController)
76 76 .controller('DashboardSettingsController', DashboardSettingsController)
77   - .controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
78 77 .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
  78 + .controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
79 79 .controller('AddWidgetController', AddWidgetController)
80 80 .directive('tbDashboardDetails', DashboardDirective)
81 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 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 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
22   - <h2 translate>dashboard.assign-dashboard-to-customer</h2>
  22 + <h2 translate>{{vm.titleText}}</h2>
23 23 <span flex></span>
24 24 <md-button class="md-icon-button" ng-click="vm.cancel()">
25 25 <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
... ... @@ -31,42 +31,17 @@
31 31 <md-dialog-content>
32 32 <div class="md-dialog-content">
33 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 38 </fieldset>
64 39 </div>
65 40 </md-dialog-content>
66 41 <md-dialog-actions layout="row">
67 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 45 </md-button>
71 46 <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
72 47 translate }}
... ...
... ... @@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
38 38 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
39 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 47 if (result) {
43 48 if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
44 49 var entities = [];
... ... @@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
71 76
72 77 ngModelCtrl.$render = function () {
73 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 84 function success(entity) {
76 85 scope.entity = entity;
77 86 },
... ... @@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
114 123 scope.selectEntityText = 'asset.select-asset';
115 124 scope.entityText = 'asset.asset';
116 125 scope.noEntitiesMatchingText = 'asset.no-assets-matching';
117   - scope.entityRequiredText = 'asset.asset-required'
  126 + scope.entityRequiredText = 'asset.asset-required';
118 127 break;
119 128 case types.entityType.device:
120 129 scope.selectEntityText = 'device.select-device';
121 130 scope.entityText = 'device.device';
122 131 scope.noEntitiesMatchingText = 'device.no-devices-matching';
123   - scope.entityRequiredText = 'device.device-required'
  132 + scope.entityRequiredText = 'device.device-required';
124 133 break;
125 134 case types.entityType.rule:
126 135 scope.selectEntityText = 'rule.select-rule';
127 136 scope.entityText = 'rule.rule';
128 137 scope.noEntitiesMatchingText = 'rule.no-rules-matching';
129   - scope.entityRequiredText = 'rule.rule-required'
  138 + scope.entityRequiredText = 'rule.rule-required';
130 139 break;
131 140 case types.entityType.plugin:
132 141 scope.selectEntityText = 'plugin.select-plugin';
133 142 scope.entityText = 'plugin.plugin';
134 143 scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
135   - scope.entityRequiredText = 'plugin.plugin-required'
  144 + scope.entityRequiredText = 'plugin.plugin-required';
136 145 break;
137 146 case types.entityType.tenant:
138 147 scope.selectEntityText = 'tenant.select-tenant';
139 148 scope.entityText = 'tenant.tenant';
140 149 scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
141   - scope.entityRequiredText = 'tenant.tenant-required'
  150 + scope.entityRequiredText = 'tenant.tenant-required';
142 151 break;
143 152 case types.entityType.customer:
144 153 scope.selectEntityText = 'customer.select-customer';
145 154 scope.entityText = 'customer.customer';
146 155 scope.noEntitiesMatchingText = 'customer.no-customers-matching';
147   - scope.entityRequiredText = 'customer.customer-required'
  156 + scope.entityRequiredText = 'customer.customer-required';
148 157 break;
149 158 case types.entityType.user:
150 159 scope.selectEntityText = 'user.select-user';
151 160 scope.entityText = 'user.user';
152 161 scope.noEntitiesMatchingText = 'user.no-users-matching';
153   - scope.entityRequiredText = 'user.user-required'
  162 + scope.entityRequiredText = 'user.user-required';
154 163 break;
155 164 case types.entityType.dashboard:
156 165 scope.selectEntityText = 'dashboard.select-dashboard';
157 166 scope.entityText = 'dashboard.dashboard';
158 167 scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
159   - scope.entityRequiredText = 'dashboard.dashboard-required'
  168 + scope.entityRequiredText = 'dashboard.dashboard-required';
160 169 break;
161 170 case types.entityType.alarm:
162 171 scope.selectEntityText = 'alarm.select-alarm';
163 172 scope.entityText = 'alarm.alarm';
164 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 181 break;
167 182 }
168 183 if (scope.entity && scope.entity.id.entityType != scope.entityType) {
... ...
... ... @@ -32,6 +32,7 @@
32 32 <tb-entity-select flex
33 33 the-form="theForm"
34 34 tb-required="true"
  35 + use-alias-entity-types="true"
35 36 ng-model="filter.singleEntity">
36 37 </tb-entity-select>
37 38 </section>
... ... @@ -78,6 +79,7 @@
78 79 <tb-entity-select flex
79 80 the-form="theForm"
80 81 tb-required="false"
  82 + use-alias-entity-types="true"
81 83 ng-model="filter.defaultStateEntity">
82 84 </tb-entity-select>
83 85 </div>
... ... @@ -123,6 +125,7 @@
123 125 the-form="theForm"
124 126 tb-required="!filter.rootStateEntity"
125 127 ng-disabled="filter.rootStateEntity"
  128 + use-alias-entity-types="true"
126 129 ng-model="filter.rootEntity">
127 130 </tb-entity-select>
128 131 </div>
... ... @@ -139,6 +142,7 @@
139 142 <tb-entity-select flex
140 143 the-form="theForm"
141 144 tb-required="false"
  145 + use-alias-entity-types="true"
142 146 ng-model="filter.defaultStateEntity">
143 147 </tb-entity-select>
144 148 </div>
... ... @@ -182,6 +186,7 @@
182 186 the-form="theForm"
183 187 tb-required="!filter.rootStateEntity"
184 188 ng-disabled="filter.rootStateEntity"
  189 + use-alias-entity-types="true"
185 190 ng-model="filter.rootEntity">
186 191 </tb-entity-select>
187 192 </div>
... ... @@ -198,6 +203,7 @@
198 203 <tb-entity-select flex
199 204 the-form="theForm"
200 205 tb-required="false"
  206 + use-alias-entity-types="true"
201 207 ng-model="filter.defaultStateEntity">
202 208 </tb-entity-select>
203 209 </div>
... ... @@ -249,6 +255,7 @@
249 255 the-form="theForm"
250 256 tb-required="!filter.rootStateEntity"
251 257 ng-disabled="filter.rootStateEntity"
  258 + use-alias-entity-types="true"
252 259 ng-model="filter.rootEntity">
253 260 </tb-entity-select>
254 261 </div>
... ... @@ -265,6 +272,7 @@
265 272 <tb-entity-select flex
266 273 the-form="theForm"
267 274 tb-required="false"
  275 + use-alias-entity-types="true"
268 276 ng-model="filter.defaultStateEntity">
269 277 </tb-entity-select>
270 278 </div>
... ...
... ... @@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) {
105 105 scope: {
106 106 theForm: '=?',
107 107 tbRequired: '=?',
108   - disabled:'=ngDisabled'
  108 + disabled:'=ngDisabled',
  109 + useAliasEntityTypes: "=?"
109 110 }
110 111 };
111 112 }
... ...
... ... @@ -20,6 +20,7 @@
20 20 the-form="theForm"
21 21 ng-disabled="disabled"
22 22 tb-required="tbRequired"
  23 + use-alias-entity-types="useAliasEntityTypes"
23 24 ng-model="model.entityType">
24 25 </tb-entity-type-select>
25 26 <tb-entity-autocomplete flex ng-if="model.entityType"
... ...
... ... @@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
39 39
40 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 44 scope.typeName = function(type) {
45 45 return type ? types.entityTypeTranslations[type].type : '';
... ... @@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
79 79 theForm: '=?',
80 80 tbRequired: '=?',
81 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 540 function success(dashboard) {
541 541 var name = dashboard.title;
542 542 name = name.toLowerCase().replace(/\W/g,"_");
543   - exportToPc(prepareExport(dashboard), name + '.json');
  543 + exportToPc(prepareDashboardExport(dashboard), name + '.json');
544 544 },
545 545 function fail(rejection) {
546 546 var message = rejection;
... ... @@ -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 564 function importDashboard($event) {
556 565 var deferred = $q.defer();
557 566 openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(
... ...
... ... @@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', [])
383 383 "idCopiedMessage": "Customer Id has been copied to clipboard",
384 384 "select-customer": "Select customer",
385 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 391 "datetime": {
389 392 "date-from": "Date from",
... ... @@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
404 407 "unassign-from-customer": "Unassign from customer",
405 408 "make-public": "Make dashboard public",
406 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 416 "no-dashboards-text": "No dashboards found",
408 417 "no-widgets": "No widgets configured",
409 418 "add-widget": "Add new widget",
... ... @@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
418 427 "add-dashboard-text": "Add new dashboard",
419 428 "assign-dashboards": "Assign dashboards",
420 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 432 "delete-dashboards": "Delete dashboards",
423 433 "unassign-dashboards": "Unassign dashboards",
424 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 510 "Please contact your administrator in order to resolve this issue.",
501 511 "select-devices": "Select devices",
502 512 "assignedToCustomer": "Assigned to customer",
  513 + "assignedToCustomers": "Assigned to customers",
503 514 "public": "Public",
504 515 "public-link": "Public link",
505 516 "copy-public-link": "Copy public link",
... ... @@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
735 746 "type-alarms": "Alarms",
736 747 "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
737 748 "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
  749 + "type-current-customer": "Current Customer",
738 750 "search": "Search entities",
739 751 "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
740 752 "entity-name": "Entity name",
... ...