Commit 00ce0707d71e0741f3d666e5a36e8fc504bd318f

Authored by Volodymyr Babak
1 parent 39591675

Added Edge UI support and assignedEdges to Dashboard

Showing 41 changed files with 873 additions and 43 deletions
... ... @@ -360,6 +360,9 @@ public abstract class BaseController {
360 360 case ENTITY_VIEW:
361 361 checkEntityViewId(new EntityViewId(entityId.getId()), operation);
362 362 return;
  363 + case EDGE:
  364 + checkEdgeId(new EdgeId(entityId.getId()), operation);
  365 + return;
363 366 default:
364 367 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
365 368 }
... ...
... ... @@ -15,7 +15,6 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
18   -import lombok.Getter;
19 18 import org.springframework.beans.factory.annotation.Value;
20 19 import org.springframework.http.HttpStatus;
21 20 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -32,10 +31,13 @@ import org.thingsboard.server.common.data.Dashboard;
32 31 import org.thingsboard.server.common.data.DashboardInfo;
33 32 import org.thingsboard.server.common.data.EntityType;
34 33 import org.thingsboard.server.common.data.ShortCustomerInfo;
  34 +import org.thingsboard.server.common.data.ShortEdgeInfo;
35 35 import org.thingsboard.server.common.data.audit.ActionType;
  36 +import org.thingsboard.server.common.data.edge.Edge;
36 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 38 import org.thingsboard.server.common.data.id.CustomerId;
38 39 import org.thingsboard.server.common.data.id.DashboardId;
  40 +import org.thingsboard.server.common.data.id.EdgeId;
39 41 import org.thingsboard.server.common.data.id.TenantId;
40 42 import org.thingsboard.server.common.data.page.TextPageData;
41 43 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -473,4 +475,218 @@ public class DashboardController extends BaseController {
473 475 throw handleException(e);
474 476 }
475 477 }
  478 +
  479 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  480 + @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST)
  481 + @ResponseBody
  482 + public Dashboard assignDashboardToEdge(@PathVariable("edgeId") String strEdgeId,
  483 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  484 + checkParameter("edgeId", strEdgeId);
  485 + checkParameter(DASHBOARD_ID, strDashboardId);
  486 + try {
  487 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  488 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  489 +
  490 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  491 + checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  492 +
  493 + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  494 +
  495 + logEntityAction(dashboardId, savedDashboard,
  496 + null,
  497 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, strEdgeId, edge.getName());
  498 +
  499 +
  500 + return savedDashboard;
  501 + } catch (Exception e) {
  502 +
  503 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  504 + null,
  505 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId, strEdgeId);
  506 +
  507 + throw handleException(e);
  508 + }
  509 + }
  510 +
  511 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  512 + @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  513 + @ResponseBody
  514 + public Dashboard unassignDashboardFromEdge(@PathVariable("edgeId") String strEdgeId,
  515 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  516 + checkParameter("edgeId", strEdgeId);
  517 + checkParameter(DASHBOARD_ID, strDashboardId);
  518 + try {
  519 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  520 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  521 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  522 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_EDGE);
  523 +
  524 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  525 +
  526 + logEntityAction(dashboardId, dashboard,
  527 + null,
  528 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edge.getId().toString(), edge.getName());
  529 +
  530 + return savedDashboard;
  531 + } catch (Exception e) {
  532 +
  533 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  534 + null,
  535 + ActionType.UNASSIGNED_FROM_EDGE, e, strDashboardId);
  536 +
  537 + throw handleException(e);
  538 + }
  539 + }
  540 +
  541 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  542 + @RequestMapping(value = "/dashboard/{dashboardId}/edges", method = RequestMethod.POST)
  543 + @ResponseBody
  544 + public Dashboard updateDashboardEdges(@PathVariable(DASHBOARD_ID) String strDashboardId,
  545 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  546 + checkParameter(DASHBOARD_ID, strDashboardId);
  547 + try {
  548 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  549 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  550 +
  551 + Set<EdgeId> edgeIds = new HashSet<>();
  552 + if (strEdgeIds != null) {
  553 + for (String strEdgeId : strEdgeIds) {
  554 + edgeIds.add(new EdgeId(toUUID(strEdgeId)));
  555 + }
  556 + }
  557 +
  558 + Set<EdgeId> addedEdgeIds = new HashSet<>();
  559 + Set<EdgeId> removedEdgeIds = new HashSet<>();
  560 + for (EdgeId edgeId : edgeIds) {
  561 + if (!dashboard.isAssignedToEdge(edgeId)) {
  562 + addedEdgeIds.add(edgeId);
  563 + }
  564 + }
  565 +
  566 + Set<ShortEdgeInfo> assignedEdges = dashboard.getAssignedEdges();
  567 + if (assignedEdges != null) {
  568 + for (ShortEdgeInfo edgeInfo : assignedEdges) {
  569 + if (!edgeIds.contains(edgeInfo.getEdgeId())) {
  570 + removedEdgeIds.add(edgeInfo.getEdgeId());
  571 + }
  572 + }
  573 + }
  574 +
  575 + if (addedEdgeIds.isEmpty() && removedEdgeIds.isEmpty()) {
  576 + return dashboard;
  577 + } else {
  578 + Dashboard savedDashboard = null;
  579 + for (EdgeId edgeId : addedEdgeIds) {
  580 + savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  581 + ShortEdgeInfo edgeInfo = savedDashboard.getAssignedEdgeInfo(edgeId);
  582 + logEntityAction(dashboardId, savedDashboard,
  583 + null,
  584 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  585 + }
  586 + for (EdgeId edgeId : removedEdgeIds) {
  587 + ShortEdgeInfo edgeInfo = dashboard.getAssignedEdgeInfo(edgeId);
  588 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  589 + logEntityAction(dashboardId, dashboard,
  590 + null,
  591 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  592 +
  593 + }
  594 + return savedDashboard;
  595 + }
  596 + } catch (Exception e) {
  597 +
  598 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  599 + null,
  600 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId);
  601 +
  602 + throw handleException(e);
  603 + }
  604 + }
  605 +
  606 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  607 + @RequestMapping(value = "/dashboard/{dashboardId}/edges/add", method = RequestMethod.POST)
  608 + @ResponseBody
  609 + public Dashboard addDashboardEdges(@PathVariable(DASHBOARD_ID) String strDashboardId,
  610 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  611 + checkParameter(DASHBOARD_ID, strDashboardId);
  612 + try {
  613 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  614 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  615 +
  616 + Set<EdgeId> edgeIds = new HashSet<>();
  617 + if (strEdgeIds != null) {
  618 + for (String strEdgeId : strEdgeIds) {
  619 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  620 + if (!dashboard.isAssignedToEdge(edgeId)) {
  621 + edgeIds.add(edgeId);
  622 + }
  623 + }
  624 + }
  625 +
  626 + if (edgeIds.isEmpty()) {
  627 + return dashboard;
  628 + } else {
  629 + Dashboard savedDashboard = null;
  630 + for (EdgeId edgeId : edgeIds) {
  631 + savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  632 + ShortEdgeInfo edgeInfo = savedDashboard.getAssignedEdgeInfo(edgeId);
  633 + logEntityAction(dashboardId, savedDashboard,
  634 + null,
  635 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  636 + }
  637 + return savedDashboard;
  638 + }
  639 + } catch (Exception e) {
  640 +
  641 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  642 + null,
  643 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId);
  644 +
  645 + throw handleException(e);
  646 + }
  647 + }
  648 +
  649 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  650 + @RequestMapping(value = "/dashboard/{dashboardId}/edges/remove", method = RequestMethod.POST)
  651 + @ResponseBody
  652 + public Dashboard removeDashboardEdges(@PathVariable(DASHBOARD_ID) String strDashboardId,
  653 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  654 + checkParameter(DASHBOARD_ID, strDashboardId);
  655 + try {
  656 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  657 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_EDGE);
  658 +
  659 + Set<EdgeId> edgeIds = new HashSet<>();
  660 + if (strEdgeIds != null) {
  661 + for (String strEdgeId : strEdgeIds) {
  662 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  663 + if (dashboard.isAssignedToEdge(edgeId)) {
  664 + edgeIds.add(edgeId);
  665 + }
  666 + }
  667 + }
  668 +
  669 + if (edgeIds.isEmpty()) {
  670 + return dashboard;
  671 + } else {
  672 + Dashboard savedDashboard = null;
  673 + for (EdgeId edgeId : edgeIds) {
  674 + ShortEdgeInfo edgeInfo = dashboard.getAssignedEdgeInfo(edgeId);
  675 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  676 + logEntityAction(dashboardId, dashboard,
  677 + null,
  678 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  679 +
  680 + }
  681 + return savedDashboard;
  682 + }
  683 + } catch (Exception e) {
  684 +
  685 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  686 + null,
  687 + ActionType.UNASSIGNED_FROM_EDGE, e, strDashboardId);
  688 +
  689 + throw handleException(e);
  690 + }
  691 + }
476 692 }
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.permission;
18 18 public enum Operation {
19 19
20 20 ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL,
21   - READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES
  21 + READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES,
  22 + ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE
22 23
23 24 }
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.Dashboard;
20 20 import org.thingsboard.server.common.data.DashboardInfo;
21 21 import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.DashboardId;
  23 +import org.thingsboard.server.common.data.id.EdgeId;
23 24 import org.thingsboard.server.common.data.id.TenantId;
24 25 import org.thingsboard.server.common.data.page.TextPageData;
25 26 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -54,4 +55,11 @@ public interface DashboardService {
54 55
55 56 void updateCustomerDashboards(TenantId tenantId, CustomerId customerId);
56 57
  58 + Dashboard assignDashboardToEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId);
  59 +
  60 + Dashboard unassignDashboardFromEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId);
  61 +
  62 + void unassignEdgeDashboards(TenantId tenantId, EdgeId edgeId);
  63 +
  64 + void updateEdgeDashboards(TenantId tenantId, EdgeId edgeId);
57 65 }
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -16,8 +16,12 @@
16 16 package org.thingsboard.server.common.data;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonProperty;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import org.thingsboard.server.common.data.edge.Edge;
19 22 import org.thingsboard.server.common.data.id.CustomerId;
20 23 import org.thingsboard.server.common.data.id.DashboardId;
  24 +import org.thingsboard.server.common.data.id.EdgeId;
21 25 import org.thingsboard.server.common.data.id.TenantId;
22 26
23 27 import java.util.*;
... ... @@ -28,6 +32,9 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
28 32 private String title;
29 33 private Set<ShortCustomerInfo> assignedCustomers;
30 34
  35 + @Getter @Setter
  36 + private Set<ShortEdgeInfo> assignedEdges;
  37 +
31 38 public DashboardInfo() {
32 39 super();
33 40 }
... ... @@ -116,6 +123,55 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
116 123 }
117 124 }
118 125
  126 + public boolean isAssignedToEdge(EdgeId edgeId) {
  127 + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null));
  128 + }
  129 +
  130 + public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) {
  131 + if (this.assignedEdges != null) {
  132 + for (ShortEdgeInfo edgeInfo : this.assignedEdges) {
  133 + if (edgeInfo.getEdgeId().equals(edgeId)) {
  134 + return edgeInfo;
  135 + }
  136 + }
  137 + }
  138 + return null;
  139 + }
  140 +
  141 + public boolean addAssignedEdge(Edge edge) {
  142 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  143 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  144 + return false;
  145 + } else {
  146 + if (this.assignedEdges == null) {
  147 + this.assignedEdges = new HashSet<>();
  148 + }
  149 + this.assignedEdges.add(edgeInfo);
  150 + return true;
  151 + }
  152 + }
  153 +
  154 + public boolean updateAssignedEdge(Edge edge) {
  155 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  156 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  157 + this.assignedEdges.remove(edgeInfo);
  158 + this.assignedEdges.add(edgeInfo);
  159 + return true;
  160 + } else {
  161 + return false;
  162 + }
  163 + }
  164 +
  165 + public boolean removeAssignedEdge(Edge edge) {
  166 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  167 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  168 + this.assignedEdges.remove(edgeInfo);
  169 + return true;
  170 + } else {
  171 + return false;
  172 + }
  173 + }
  174 +
119 175 @Override
120 176 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
121 177 public String getName() {
... ...
  1 +package org.thingsboard.server.common.data;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +import org.thingsboard.server.common.data.id.EdgeId;
  7 +
  8 +@Data
  9 +@NoArgsConstructor
  10 +@AllArgsConstructor
  11 +public class ShortEdgeInfo {
  12 +
  13 + private EdgeId edgeId;
  14 + private String title;
  15 +}
... ...
... ... @@ -40,7 +40,9 @@ public enum ActionType {
40 40 ALARM_CLEAR(false),
41 41 LOGIN(false),
42 42 LOGOUT(false),
43   - LOCKOUT(false);
  43 + LOCKOUT(false),
  44 + ASSIGNED_TO_EDGE(false), // log edge name
  45 + UNASSIGNED_FROM_EDGE(false); // log edge name
44 46
45 47 private final boolean isRead;
46 48
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.common.data.edge;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
18 19 import com.fasterxml.jackson.databind.JsonNode;
19 20 import lombok.EqualsAndHashCode;
20 21 import lombok.Getter;
... ... @@ -24,6 +25,8 @@ import org.thingsboard.server.common.data.HasCustomerId;
24 25 import org.thingsboard.server.common.data.HasName;
25 26 import org.thingsboard.server.common.data.HasTenantId;
26 27 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  28 +import org.thingsboard.server.common.data.ShortCustomerInfo;
  29 +import org.thingsboard.server.common.data.ShortEdgeInfo;
27 30 import org.thingsboard.server.common.data.id.CustomerId;
28 31 import org.thingsboard.server.common.data.id.EdgeId;
29 32 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -40,8 +43,8 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
40 43 private CustomerId customerId;
41 44 private String name;
42 45 private String type;
  46 + private String label;
43 47 private transient JsonNode configuration;
44   - private transient JsonNode additionalInfo;
45 48
46 49 public Edge() {
47 50 super();
... ... @@ -58,7 +61,11 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
58 61 this.type = edge.getType();
59 62 this.name = edge.getName();
60 63 this.configuration = edge.getConfiguration();
61   - this.additionalInfo = edge.getAdditionalInfo();
  64 + }
  65 +
  66 + @JsonIgnore
  67 + public ShortEdgeInfo toShortEdgeInfo() {
  68 + return new ShortEdgeInfo(id, name);
62 69 }
63 70
64 71 @Override
... ...
... ... @@ -21,6 +21,7 @@ public enum RelationTypeGroup {
21 21 ALARM,
22 22 DASHBOARD,
23 23 RULE_CHAIN,
24   - RULE_NODE
  24 + RULE_NODE,
  25 + EDGE
25 26
26 27 }
... ...
... ... @@ -260,6 +260,22 @@ public class AuditLogServiceImpl implements AuditLogService {
260 260 actionData.put("os", os);
261 261 actionData.put("device", device);
262 262 break;
  263 + case ASSIGNED_TO_EDGE:
  264 + strEntityId = extractParameter(String.class, 0, additionalInfo);
  265 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  266 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  267 + actionData.put("entityId", strEntityId);
  268 + actionData.put("assignedEdgeId", strEdgeId);
  269 + actionData.put("assignedEdgeName", strEdgeName);
  270 + break;
  271 + case UNASSIGNED_FROM_EDGE:
  272 + strEntityId = extractParameter(String.class, 0, additionalInfo);
  273 + strEdgeId = extractParameter(String.class, 1, additionalInfo);
  274 + strEdgeName = extractParameter(String.class, 2, additionalInfo);
  275 + actionData.put("entityId", strEntityId);
  276 + actionData.put("unassignedEdgeId", strEdgeId);
  277 + actionData.put("unassignedEdgeName", strEdgeName);
  278 + break;
263 279 }
264 280 return actionData;
265 281 }
... ...
... ... @@ -23,6 +23,7 @@ import org.springframework.stereotype.Component;
23 23 import org.thingsboard.server.common.data.DashboardInfo;
24 24 import org.thingsboard.server.common.data.EntityType;
25 25 import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.EdgeId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.page.TextPageLink;
28 29 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -88,4 +89,19 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
88 89 });
89 90 }
90 91
  92 + @Override
  93 + public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
  94 + log.debug("Try to find dashboards by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
  95 +
  96 + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.DASHBOARD, pageLink);
  97 +
  98 + return Futures.transformAsync(relations, input -> {
  99 + List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
  100 + for (EntityRelation relation : input) {
  101 + dashboardFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId()));
  102 + }
  103 + return Futures.successfulAsList(dashboardFutures);
  104 + });
  105 + }
  106 +
91 107 }
... ...
... ... @@ -48,4 +48,15 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
48 48 */
49 49 ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink);
50 50
  51 +
  52 + /**
  53 + * Find dashboards by tenantId, edgeId and page link.
  54 + *
  55 + * @param tenantId the tenantId
  56 + * @param edgeId the edgeId
  57 + * @param pageLink the page link
  58 + * @return the list of dashboard objects
  59 + */
  60 + ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink);
  61 +
51 62 }
... ...
... ... @@ -26,8 +26,10 @@ import org.thingsboard.server.common.data.Customer;
26 26 import org.thingsboard.server.common.data.Dashboard;
27 27 import org.thingsboard.server.common.data.DashboardInfo;
28 28 import org.thingsboard.server.common.data.Tenant;
  29 +import org.thingsboard.server.common.data.edge.Edge;
29 30 import org.thingsboard.server.common.data.id.CustomerId;
30 31 import org.thingsboard.server.common.data.id.DashboardId;
  32 +import org.thingsboard.server.common.data.id.EdgeId;
31 33 import org.thingsboard.server.common.data.id.TenantId;
32 34 import org.thingsboard.server.common.data.page.TextPageData;
33 35 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -36,6 +38,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
36 38 import org.thingsboard.server.common.data.relation.EntityRelation;
37 39 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
38 40 import org.thingsboard.server.dao.customer.CustomerDao;
  41 +import org.thingsboard.server.dao.edge.EdgeDao;
39 42 import org.thingsboard.server.dao.entity.AbstractEntityService;
40 43 import org.thingsboard.server.dao.exception.DataValidationException;
41 44 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -67,6 +70,9 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
67 70
68 71 @Autowired
69 72 private CustomerDao customerDao;
  73 +
  74 + @Autowired
  75 + private EdgeDao edgeDao;
70 76
71 77 @Override
72 78 public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
... ... @@ -228,6 +234,80 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
228 234 new CustomerDashboardsUpdater(customer).removeEntities(tenantId, customer);
229 235 }
230 236
  237 + @Override
  238 + public Dashboard assignDashboardToEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId) {
  239 + Dashboard dashboard = findDashboardById(tenantId, dashboardId);
  240 + Edge edge = edgeDao.findById(tenantId, edgeId.getId());
  241 + if (edge == null) {
  242 + throw new DataValidationException("Can't assign dashboard to non-existent edge!");
  243 + }
  244 + if (!edge.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
  245 + throw new DataValidationException("Can't assign dashboard to edge from different tenant!");
  246 + }
  247 + if (dashboard.addAssignedEdge(edge)) {
  248 + try {
  249 + createRelation(tenantId, new EntityRelation(edgeId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
  250 + } catch (ExecutionException | InterruptedException e) {
  251 + log.warn("[{}] Failed to create dashboard relation. Edge Id: [{}]", dashboardId, edgeId);
  252 + throw new RuntimeException(e);
  253 + }
  254 + return saveDashboard(dashboard);
  255 + } else {
  256 + return dashboard;
  257 + }
  258 + }
  259 +
  260 + @Override
  261 + public Dashboard unassignDashboardFromEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId) {
  262 + Dashboard dashboard = findDashboardById(tenantId, dashboardId);
  263 + Edge edge = edgeDao.findById(tenantId, edgeId.getId());
  264 + if (edge == null) {
  265 + throw new DataValidationException("Can't unassign dashboard from non-existent edge!");
  266 + }
  267 + if (dashboard.removeAssignedEdge(edge)) {
  268 + try {
  269 + deleteRelation(tenantId, new EntityRelation(edgeId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE));
  270 + } catch (ExecutionException | InterruptedException e) {
  271 + log.warn("[{}] Failed to delete dashboard relation. Edge Id: [{}]", dashboardId, edgeId);
  272 + throw new RuntimeException(e);
  273 + }
  274 + return saveDashboard(dashboard);
  275 + } else {
  276 + return dashboard;
  277 + }
  278 + }
  279 +
  280 + @Override
  281 + public void unassignEdgeDashboards(TenantId tenantId, EdgeId edgeId) {
  282 + log.trace("Executing unassignEdgeDashboards, edgeId [{}]", edgeId);
  283 + Validator.validateId(edgeId, "Incorrect edgeId " + edgeId);
  284 + Edge edge = edgeDao.findById(tenantId, edgeId.getId());
  285 + if (edge == null) {
  286 + throw new DataValidationException("Can't unassign dashboards from non-existent edge!");
  287 + }
  288 + new EdgeDashboardsUnassigner(edge).removeEntities(tenantId, edge);
  289 + }
  290 +
  291 + @Override
  292 + public void updateEdgeDashboards(TenantId tenantId, EdgeId edgeId) {
  293 + log.trace("Executing updateEdgeDashboards, edgeId [{}]", edgeId);
  294 + Validator.validateId(edgeId, "Incorrect edgeId " + edgeId);
  295 + Edge edge = edgeDao.findById(tenantId, edgeId.getId());
  296 + if (edge == null) {
  297 + throw new DataValidationException("Can't update dashboards for non-existent edge!");
  298 + }
  299 + new EdgeDashboardsUpdater(edge).removeEntities(tenantId, edge);
  300 + }
  301 +
  302 + private Dashboard updateAssignedEdge(TenantId tenantId, DashboardId dashboardId, Edge edge) {
  303 + Dashboard dashboard = findDashboardById(tenantId, dashboardId);
  304 + if (dashboard.updateAssignedEdge(edge)) {
  305 + return saveDashboard(dashboard);
  306 + } else {
  307 + return dashboard;
  308 + }
  309 + }
  310 +
231 311 private DataValidator<Dashboard> dashboardValidator =
232 312 new DataValidator<Dashboard>() {
233 313 @Override
... ... @@ -310,4 +390,53 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
310 390
311 391 }
312 392
  393 + private class EdgeDashboardsUnassigner extends TimePaginatedRemover<Edge, DashboardInfo> {
  394 +
  395 + private Edge edge;
  396 +
  397 + EdgeDashboardsUnassigner(Edge edge) {
  398 + this.edge = edge;
  399 + }
  400 +
  401 + @Override
  402 + protected List<DashboardInfo> findEntities(TenantId tenantId, Edge edge, TimePageLink pageLink) {
  403 + try {
  404 + return dashboardInfoDao.findDashboardsByTenantIdAndEdgeId(edge.getTenantId().getId(), edge.getId().getId(), pageLink).get();
  405 + } catch (InterruptedException | ExecutionException e) {
  406 + log.warn("Failed to get dashboards by tenantId [{}] and edgeId [{}].", edge.getTenantId().getId(), edge.getId().getId());
  407 + throw new RuntimeException(e);
  408 + }
  409 + }
  410 +
  411 + @Override
  412 + protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
  413 + unassignDashboardFromEdge(edge.getTenantId(), new DashboardId(entity.getUuidId()), this.edge.getId());
  414 + }
  415 +
  416 + }
  417 +
  418 + private class EdgeDashboardsUpdater extends TimePaginatedRemover<Edge, DashboardInfo> {
  419 +
  420 + private Edge edge;
  421 +
  422 + EdgeDashboardsUpdater(Edge edge) {
  423 + this.edge = edge;
  424 + }
  425 +
  426 + @Override
  427 + protected List<DashboardInfo> findEntities(TenantId tenantId, Edge edge, TimePageLink pageLink) {
  428 + try {
  429 + return dashboardInfoDao.findDashboardsByTenantIdAndEdgeId(edge.getTenantId().getId(), edge.getId().getId(), pageLink).get();
  430 + } catch (InterruptedException | ExecutionException e) {
  431 + log.warn("Failed to get dashboards by tenantId [{}] and edgeId [{}].", edge.getTenantId().getId(), edge.getId().getId());
  432 + throw new RuntimeException(e);
  433 + }
  434 + }
  435 +
  436 + @Override
  437 + protected void removeEntity(TenantId tenantId, DashboardInfo entity) {
  438 + updateAssignedEdge(edge.getTenantId(), new DashboardId(entity.getUuidId()), this.edge);
  439 + }
  440 +
  441 + }
313 442 }
... ...
... ... @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
41 41 import org.thingsboard.server.common.data.relation.EntityRelation;
42 42 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
43 43 import org.thingsboard.server.dao.customer.CustomerDao;
  44 +import org.thingsboard.server.dao.dashboard.DashboardService;
44 45 import org.thingsboard.server.dao.entity.AbstractEntityService;
45 46 import org.thingsboard.server.dao.exception.DataValidationException;
46 47 import org.thingsboard.server.dao.service.DataValidator;
... ... @@ -84,6 +85,9 @@ public class BaseEdgeService extends AbstractEntityService implements EdgeServic
84 85 @Autowired
85 86 private CacheManager cacheManager;
86 87
  88 + @Autowired
  89 + private DashboardService dashboardService;
  90 +
87 91 @Override
88 92 public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) {
89 93 log.trace("Executing findEdgeById [{}]", edgeId);
... ... @@ -112,7 +116,9 @@ public class BaseEdgeService extends AbstractEntityService implements EdgeServic
112 116 public Edge saveEdge(Edge edge) {
113 117 log.trace("Executing saveEdge [{}]", edge);
114 118 edgeValidator.validate(edge, Edge::getTenantId);
115   - return edgeDao.save(edge.getTenantId(), edge);
  119 + Edge savedEdge = edgeDao.save(edge.getTenantId(), edge);
  120 + dashboardService.updateEdgeDashboards(savedEdge.getTenantId(), savedEdge.getId());
  121 + return savedEdge;
116 122 }
117 123
118 124 @Override
... ... @@ -144,6 +150,8 @@ public class BaseEdgeService extends AbstractEntityService implements EdgeServic
144 150 Cache cache = cacheManager.getCache(EDGE_CACHE);
145 151 cache.evict(list);
146 152
  153 + dashboardService.unassignEdgeDashboards(tenantId, edgeId);
  154 +
147 155 edgeDao.removeById(tenantId, edgeId.getId());
148 156 }
149 157
... ...
... ... @@ -292,6 +292,7 @@ public class ModelConstants {
292 292 public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
293 293 public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
294 294 public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
  295 + public static final String DASHBOARD_ASSIGNED_EDGES_PROPERTY = "assigned_edges";
295 296
296 297 public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
297 298
... ... @@ -335,6 +336,7 @@ public class ModelConstants {
335 336 public static final String RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY = "first_rule_node_id";
336 337 public static final String RULE_CHAIN_ROOT_PROPERTY = "root";
337 338 public static final String RULE_CHAIN_CONFIGURATION_PROPERTY = "configuration";
  339 + public static final String RULE_CHAIN_ASSIGNED_EDGES_PROPERTY = "assigned_edges";
338 340
339 341 public static final String RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_chain_by_tenant_and_search_text";
340 342
... ... @@ -354,6 +356,7 @@ public class ModelConstants {
354 356 public static final String EDGE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
355 357 public static final String EDGE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
356 358 public static final String EDGE_NAME_PROPERTY = "name";
  359 + public static final String EDGE_LABEL_PROPERTY = "label";
357 360 public static final String EDGE_TYPE_PROPERTY = "type";
358 361 public static final String EDGE_CONFIGURATION_PROPERTY = "configuration";
359 362 public static final String EDGE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
... ...
... ... @@ -35,6 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ADDITIONAL_IN
35 35 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
36 36 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CONFIGURATION_PROPERTY;
37 37 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY;
  38 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY;
38 39 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
39 40 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
40 41 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TYPE_PROPERTY;
... ... @@ -63,6 +64,9 @@ public class EdgeEntity implements SearchTextEntity<Edge> {
63 64 @Column(name = EDGE_NAME_PROPERTY)
64 65 private String name;
65 66
  67 + @Column(name = EDGE_LABEL_PROPERTY)
  68 + private String label;
  69 +
66 70 @Column(name = SEARCH_TEXT_PROPERTY)
67 71 private String searchText;
68 72
... ... @@ -85,6 +89,7 @@ public class EdgeEntity implements SearchTextEntity<Edge> {
85 89 }
86 90 this.type = edge.getType();
87 91 this.name = edge.getName();
  92 + this.label = edge.getLabel();
88 93 this.configuration = edge.getConfiguration();
89 94 this.additionalInfo = edge.getAdditionalInfo();
90 95 }
... ... @@ -106,6 +111,7 @@ public class EdgeEntity implements SearchTextEntity<Edge> {
106 111 }
107 112 edge.setType(type);
108 113 edge.setName(name);
  114 + edge.setLabel(label);
109 115 edge.setConfiguration(configuration);
110 116 edge.setAdditionalInfo(additionalInfo);
111 117 return edge;
... ...
... ... @@ -28,6 +28,7 @@ import org.hibernate.annotations.TypeDef;
28 28 import org.springframework.util.StringUtils;
29 29 import org.thingsboard.server.common.data.Dashboard;
30 30 import org.thingsboard.server.common.data.ShortCustomerInfo;
  31 +import org.thingsboard.server.common.data.ShortEdgeInfo;
31 32 import org.thingsboard.server.common.data.id.DashboardId;
32 33 import org.thingsboard.server.common.data.id.TenantId;
33 34 import org.thingsboard.server.dao.model.BaseSqlEntity;
... ... @@ -52,6 +53,8 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
52 53 private static final ObjectMapper objectMapper = new ObjectMapper();
53 54 private static final JavaType assignedCustomersType =
54 55 objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
  56 + private static final JavaType assignedEdgesType =
  57 + objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortEdgeInfo.class);
55 58
56 59 @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
57 60 private String tenantId;
... ... @@ -65,6 +68,9 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
65 68 @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
66 69 private String assignedCustomers;
67 70
  71 + @Column(name = ModelConstants.DASHBOARD_ASSIGNED_EDGES_PROPERTY)
  72 + private String assignedEdges;
  73 +
68 74 @Type(type = "json")
69 75 @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
70 76 private JsonNode configuration;
... ... @@ -88,6 +94,13 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
88 94 log.error("Unable to serialize assigned customers to string!", e);
89 95 }
90 96 }
  97 + if (dashboard.getAssignedEdges() != null) {
  98 + try {
  99 + this.assignedEdges = objectMapper.writeValueAsString(dashboard.getAssignedEdges());
  100 + } catch (JsonProcessingException e) {
  101 + log.error("Unable to serialize assigned edges to string!", e);
  102 + }
  103 + }
91 104 this.configuration = dashboard.getConfiguration();
92 105 }
93 106
... ... @@ -116,6 +129,13 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
116 129 log.warn("Unable to parse assigned customers!", e);
117 130 }
118 131 }
  132 + if (!StringUtils.isEmpty(assignedEdges)) {
  133 + try {
  134 + dashboard.setAssignedEdges(objectMapper.readValue(assignedEdges, assignedEdgesType));
  135 + } catch (IOException e) {
  136 + log.warn("Unable to parse assigned edges!", e);
  137 + }
  138 + }
119 139 dashboard.setConfiguration(configuration);
120 140 return dashboard;
121 141 }
... ...
... ... @@ -38,6 +38,7 @@ import javax.persistence.Table;
38 38 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY;
39 39 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
40 40 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY;
41 42 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
42 43 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
43 44 import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TYPE_PROPERTY;
... ... @@ -62,6 +63,9 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<
62 63 @Column(name = EDGE_NAME_PROPERTY)
63 64 private String name;
64 65
  66 + @Column(name = EDGE_LABEL_PROPERTY)
  67 + private String label;
  68 +
65 69 @Column(name = SEARCH_TEXT_PROPERTY)
66 70 private String searchText;
67 71
... ... @@ -89,6 +93,7 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<
89 93 }
90 94 this.type = edge.getType();
91 95 this.name = edge.getName();
  96 + this.label = edge.getLabel();
92 97 this.configuration = edge.getConfiguration();
93 98 this.additionalInfo = edge.getAdditionalInfo();
94 99 }
... ... @@ -119,6 +124,7 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<
119 124 }
120 125 edge.setType(type);
121 126 edge.setName(name);
  127 + edge.setLabel(label);
122 128 edge.setConfiguration(configuration);
123 129 edge.setAdditionalInfo(additionalInfo);
124 130 return edge;
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.DashboardInfo;
26 26 import org.thingsboard.server.common.data.EntityType;
27 27 import org.thingsboard.server.common.data.UUIDConverter;
28 28 import org.thingsboard.server.common.data.id.CustomerId;
  29 +import org.thingsboard.server.common.data.id.EdgeId;
29 30 import org.thingsboard.server.common.data.id.TenantId;
30 31 import org.thingsboard.server.common.data.page.TextPageLink;
31 32 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -93,4 +94,19 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
93 94 return Futures.successfulAsList(dashboardFutures);
94 95 });
95 96 }
  97 +
  98 + @Override
  99 + public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink) {
  100 + log.debug("Try to find dashboards by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);
  101 +
  102 + ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new TenantId(tenantId), new EdgeId(edgeId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE, EntityType.DASHBOARD, pageLink);
  103 +
  104 + return Futures.transformAsync(relations, input -> {
  105 + List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
  106 + for (EntityRelation relation : input) {
  107 + dashboardFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId()));
  108 + }
  109 + return Futures.successfulAsList(dashboardFutures);
  110 + });
  111 + }
96 112 }
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -256,6 +256,7 @@ CREATE TABLE IF NOT EXISTS edge (
256 256 configuration varchar(10000000),
257 257 type varchar(255),
258 258 name varchar(255),
  259 + label varchar(255),
259 260 search_text varchar(255),
260 261 tenant_id varchar(31)
261 262 );
\ No newline at end of file
... ...
... ... @@ -25,7 +25,9 @@ import org.thingsboard.server.common.data.Customer;
25 25 import org.thingsboard.server.common.data.Dashboard;
26 26 import org.thingsboard.server.common.data.DashboardInfo;
27 27 import org.thingsboard.server.common.data.Tenant;
  28 +import org.thingsboard.server.common.data.edge.Edge;
28 29 import org.thingsboard.server.common.data.id.CustomerId;
  30 +import org.thingsboard.server.common.data.id.EdgeId;
29 31 import org.thingsboard.server.common.data.id.TenantId;
30 32 import org.thingsboard.server.common.data.page.TextPageData;
31 33 import org.thingsboard.server.common.data.page.TextPageLink;
... ... @@ -328,4 +330,38 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
328 330 tenantService.deleteTenant(tenantId);
329 331 }
330 332
  333 + @Test(expected = DataValidationException.class)
  334 + public void testAssignDashboardToNonExistentEdge() {
  335 + Dashboard dashboard = new Dashboard();
  336 + dashboard.setTitle("My dashboard");
  337 + dashboard.setTenantId(tenantId);
  338 + dashboard = dashboardService.saveDashboard(dashboard);
  339 + try {
  340 + dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), new EdgeId(UUIDs.timeBased()));
  341 + } finally {
  342 + dashboardService.deleteDashboard(tenantId, dashboard.getId());
  343 + }
  344 + }
  345 +
  346 + @Test(expected = DataValidationException.class)
  347 + public void testAssignDashboardToEdgeFromDifferentTenant() {
  348 + Dashboard dashboard = new Dashboard();
  349 + dashboard.setTitle("My dashboard");
  350 + dashboard.setTenantId(tenantId);
  351 + dashboard = dashboardService.saveDashboard(dashboard);
  352 + Tenant tenant = new Tenant();
  353 + tenant.setTitle("Test different tenant");
  354 + tenant = tenantService.saveTenant(tenant);
  355 + Edge edge = new Edge();
  356 + edge.setTenantId(tenant.getId());
  357 + edge.setName("Test different edge");
  358 + edge = edgeService.saveEdge(edge);
  359 + try {
  360 + dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId());
  361 + } finally {
  362 + dashboardService.deleteDashboard(tenantId, dashboard.getId());
  363 + tenantService.deleteTenant(tenant.getId());
  364 + }
  365 + }
  366 +
331 367 }
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -5,7 +5,7 @@
5 5 * you may not use this file except in compliance with the License.
6 6 * You may obtain a copy of the License at
7 7 *
8   - * http://www.apache.org/licenses/LICENSE-2.0
  8 + * http://www.apache.org/licenses/LICENSE-2.0
9 9 *
10 10 * Unless required by applicable law or agreed to in writing, software
11 11 * distributed under the License is distributed on an "AS IS" BASIS,
... ...
... ... @@ -18,14 +18,19 @@ export default angular.module('thingsboard.api.edge', [])
18 18 .name;
19 19
20 20 /*@ngInject*/
21   -function EdgeService($http, $q) {
  21 +function EdgeService($http, $q, customerService) {
22 22
23 23 var service = {
24 24 getEdges: getEdges,
25 25 getEdgesByIds: getEdgesByIds,
26 26 getEdge: getEdge,
27 27 deleteEdge: deleteEdge,
28   - saveEdge: saveEdge
  28 + saveEdge: saveEdge,
  29 + getEdgeTypes: getEdgeTypes,
  30 + getTenantEdges: getTenantEdges,
  31 + assignEdgeToCustomer: assignEdgeToCustomer,
  32 + unassignEdgeFromCustomer: unassignEdgeFromCustomer,
  33 + makeEdgePublic: makeEdgePublic
29 34 };
30 35
31 36 return service;
... ... @@ -108,4 +113,83 @@ function EdgeService($http, $q) {
108 113 });
109 114 return deferred.promise;
110 115 }
  116 +
  117 + function getEdgeTypes(config) {
  118 + var deferred = $q.defer();
  119 + var url = '/api/edge/types';
  120 + $http.get(url, config).then(function success(response) {
  121 + deferred.resolve(response.data);
  122 + }, function fail() {
  123 + deferred.reject();
  124 + });
  125 + return deferred.promise;
  126 + }
  127 +
  128 + function getTenantEdges(pageLink, applyCustomersInfo, config, type) {
  129 + var deferred = $q.defer();
  130 + var url = '/api/tenant/edges?limit=' + pageLink.limit;
  131 + if (angular.isDefined(pageLink.textSearch)) {
  132 + url += '&textSearch=' + pageLink.textSearch;
  133 + }
  134 + if (angular.isDefined(pageLink.idOffset)) {
  135 + url += '&idOffset=' + pageLink.idOffset;
  136 + }
  137 + if (angular.isDefined(pageLink.textOffset)) {
  138 + url += '&textOffset=' + pageLink.textOffset;
  139 + }
  140 + if (angular.isDefined(type) && type.length) {
  141 + url += '&type=' + type;
  142 + }
  143 + $http.get(url, config).then(function success(response) {
  144 + if (applyCustomersInfo) {
  145 + customerService.applyAssignedCustomersInfo(response.data.data).then(
  146 + function success(data) {
  147 + response.data.data = data;
  148 + deferred.resolve(response.data);
  149 + },
  150 + function fail() {
  151 + deferred.reject();
  152 + }
  153 + );
  154 + } else {
  155 + deferred.resolve(response.data);
  156 + }
  157 + }, function fail() {
  158 + deferred.reject();
  159 + });
  160 + return deferred.promise;
  161 + }
  162 +
  163 + function assignEdgeToCustomer(customerId, edgeId) {
  164 + var deferred = $q.defer();
  165 + var url = '/api/customer/' + customerId + '/edge/' + edgeId;
  166 + $http.post(url, null).then(function success(response) {
  167 + deferred.resolve(response.data);
  168 + }, function fail() {
  169 + deferred.reject();
  170 + });
  171 + return deferred.promise;
  172 + }
  173 +
  174 + function unassignEdgeFromCustomer(edgeId) {
  175 + var deferred = $q.defer();
  176 + var url = '/api/customer/edge/' + edgeId;
  177 + $http.delete(url).then(function success(response) {
  178 + deferred.resolve(response.data);
  179 + }, function fail() {
  180 + deferred.reject();
  181 + });
  182 + return deferred.promise;
  183 + }
  184 +
  185 + function makeEdgePublic(edgeId) {
  186 + var deferred = $q.defer();
  187 + var url = '/api/customer/public/edge/' + edgeId;
  188 + $http.post(url, null).then(function success(response) {
  189 + deferred.resolve(response.data);
  190 + }, function fail() {
  191 + deferred.reject();
  192 + });
  193 + return deferred.promise;
  194 + }
111 195 }
... ...
... ... @@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.entity', [thingsboardTypes])
22 22 /*@ngInject*/
23 23 function EntityService($http, $q, $filter, $translate, $log, userService, deviceService, assetService, tenantService,
24 24 customerService, ruleChainService, dashboardService, entityRelationService, attributeService,
25   - entityViewService, types, utils) {
  25 + entityViewService, edgeService, types, utils) {
26 26 var service = {
27 27 getEntity: getEntity,
28 28 getEntities: getEntities,
... ... @@ -77,6 +77,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
77 77 case types.entityType.alarm:
78 78 $log.error('Get Alarm Entity is not implemented!');
79 79 break;
  80 + case types.entityType.edge:
  81 + promise = edgeService.getEdge(entityId, true, config);
  82 + break;
80 83 }
81 84 return promise;
82 85 }
... ... @@ -159,6 +162,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
159 162 case types.entityType.alarm:
160 163 $log.error('Get Alarm Entity is not implemented!');
161 164 break;
  165 + case types.entityType.edge:
  166 + promise = getEntitiesByIdsPromise(
  167 + (id) => edgeService.getEdge(id, config), entityIds);
  168 + break;
162 169 }
163 170 return promise;
164 171 }
... ... @@ -285,6 +292,13 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
285 292 case types.entityType.alarm:
286 293 $log.error('Get Alarm Entities is not implemented!');
287 294 break;
  295 + case types.entityType.edge:
  296 + if (user.authority === 'CUSTOMER_USER') {
  297 + promise = edgeService.getCustomerEdges(customerId, pageLink, false, config, subType);
  298 + } else {
  299 + promise = edgeService.getTenantEdges(pageLink, false, config, subType);
  300 + }
  301 + break;
288 302 }
289 303 return promise;
290 304 }
... ... @@ -655,6 +669,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
655 669 } else if (filter.type == types.aliasFilterType.entityViewSearchQuery.value) {
656 670 searchQuery.entityViewTypes = filter.entityViewTypes;
657 671 findByQueryPromise = entityViewService.findByQuery(searchQuery, false, {ignoreLoading: true});
  672 + } else if (filter.type == types.aliasFilterType.edgeSearchQuery.value) {
  673 + searchQuery.edgeTypes = filter.edgeTypes;
  674 + findByQueryPromise = edgeService.findByQuery(searchQuery, false, {ignoreLoading: true});
658 675 }
659 676 findByQueryPromise.then(
660 677 function success(entities) {
... ... @@ -699,6 +716,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
699 716 return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
700 717 case types.aliasFilterType.entityViewType.value:
701 718 return entityTypes.indexOf(types.entityType.entityView) > -1 ? true : false;
  719 + case types.aliasFilterType.edgeType.value:
  720 + return entityTypes.indexOf(types.entityType.edge) > -1 ? true : false;
702 721 case types.aliasFilterType.relationsQuery.value:
703 722 if (filter.filters && filter.filters.length) {
704 723 var match = false;
... ... @@ -726,6 +745,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
726 745 return entityTypes.indexOf(types.entityType.device) > -1 ? true : false;
727 746 case types.aliasFilterType.entityViewSearchQuery.value:
728 747 return entityTypes.indexOf(types.entityType.entityView) > -1 ? true : false;
  748 + case types.aliasFilterType.edgeSearchQuery.value:
  749 + return entityTypes.indexOf(types.entityType.edge) > -1 ? true : false;
729 750 }
730 751 }
731 752 return false;
... ... @@ -755,6 +776,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
755 776 return entityType === types.entityType.device;
756 777 case types.aliasFilterType.entityViewSearchQuery.value:
757 778 return entityType === types.entityType.entityView;
  779 + case types.aliasFilterType.edgeSearchQuery.value:
  780 + return entityType === types.entityType.edge;
758 781 }
759 782 return false;
760 783 }
... ... @@ -796,6 +819,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
796 819 entityTypes.device = types.entityType.device;
797 820 entityTypes.asset = types.entityType.asset;
798 821 entityTypes.entityView = types.entityType.entityView;
  822 + entityTypes.edge = types.entityType.edge;
799 823 entityTypes.tenant = types.entityType.tenant;
800 824 entityTypes.customer = types.entityType.customer;
801 825 entityTypes.dashboard = types.entityType.dashboard;
... ... @@ -807,6 +831,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
807 831 entityTypes.device = types.entityType.device;
808 832 entityTypes.asset = types.entityType.asset;
809 833 entityTypes.entityView = types.entityType.entityView;
  834 + entityTypes.edge = types.entityType.edge;
810 835 entityTypes.customer = types.entityType.customer;
811 836 entityTypes.dashboard = types.entityType.dashboard;
812 837 if (useAliasEntityTypes) {
... ... @@ -1041,6 +1066,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1041 1066 findByQueryPromise = assetService.findByQuery(entitySearchQuery, true, {ignoreLoading: true});
1042 1067 } else if (entityType == types.entityType.device) {
1043 1068 findByQueryPromise = deviceService.findByQuery(entitySearchQuery, true, {ignoreLoading: true});
  1069 + } else if (entityType == types.entityType.entityView) {
  1070 + findByQueryPromise = entityViewService.findByQuery(entitySearchQuery, true, {ignoreLoading: true});
1044 1071 }
1045 1072 findByQueryPromise.then(
1046 1073 function success(entities) {
... ... @@ -1220,6 +1247,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1220 1247 return deviceService.deleteDevice(entityId.id);
1221 1248 } else if (entityId.entityType == types.entityType.entityView) {
1222 1249 return entityViewService.deleteEntityView(entityId.id);
  1250 + } else if (entityId.entityType == types.entityType.edge) {
  1251 + return edgeService.deleteEdge(entityId.id);
1223 1252 }
1224 1253 }
1225 1254
... ... @@ -1327,6 +1356,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1327 1356 return deviceService.saveDevice(entity);
1328 1357 } else if (entityType == types.entityType.entityView) {
1329 1358 return entityViewService.saveEntityView(entity);
  1359 + } else if (entityType == types.entityType.edge) {
  1360 + return edgeService.saveEdge(entity);
1330 1361 }
1331 1362 }
1332 1363
... ... @@ -1457,6 +1488,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1457 1488 searchQuery.deviceTypes = entitySubTypes;
1458 1489 } else if (entityType == types.entityType.entityView) {
1459 1490 searchQuery.entityViewTypes = entitySubTypes;
  1491 + } else if (entityType == types.entityType.edge) {
  1492 + searchQuery.edgeTypes = entitySubTypes;
1460 1493 } else {
1461 1494 return null; //Not supported
1462 1495 }
... ...
... ... @@ -222,6 +222,12 @@ export default angular.module('thingsboard.types', [])
222 222 },
223 223 "LOCKOUT": {
224 224 name: "audit-log.type-lockout"
  225 + },
  226 + "ASSIGNED_TO_EDGE": {
  227 + name: "audit-log.type-assigned-to-edge"
  228 + },
  229 + "UNASSIGNED_FROM_EDGE": {
  230 + name: "audit-log.type-unassigned-from-edge"
225 231 }
226 232 },
227 233 auditLogActionStatus: {
... ... @@ -269,6 +275,10 @@ export default angular.module('thingsboard.types', [])
269 275 value: 'entityViewType',
270 276 name: 'alias.filter-type-entity-view-type'
271 277 },
  278 + edgeType: {
  279 + value: 'edgeType',
  280 + name: 'alias.filter-type-edge-type'
  281 + },
272 282 relationsQuery: {
273 283 value: 'relationsQuery',
274 284 name: 'alias.filter-type-relations-query'
... ... @@ -284,6 +294,10 @@ export default angular.module('thingsboard.types', [])
284 294 entityViewSearchQuery: {
285 295 value: 'entityViewSearchQuery',
286 296 name: 'alias.filter-type-entity-view-search-query'
  297 + },
  298 + edgeSearchQuery: {
  299 + value: 'edgeSearchQuery',
  300 + name: 'alias.filter-type-edge-search-query'
287 301 }
288 302 },
289 303 direction: {
... ...
... ... @@ -16,6 +16,8 @@
16 16
17 17 -->
18 18 <div flex layout="column">
19   - <div flex style="text-transform: uppercase;">{{ vm.types.edgeType[vm.item.type].name | translate }}</div>
  19 + <div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
20 20 <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
  21 + <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'edge.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
  22 + <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isPublic()">{{'edge.public' | translate}}</div>
21 23 </div>
... ...
... ... @@ -15,12 +15,15 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-button ng-click="onExportEdge({event: $event})"
19   - ng-show="!isEdit"
20   - class="md-raised md-primary">{{ 'edge.export' | translate }}</md-button>
  18 +<md-button ng-click="onAssignToCustomer({event: $event})"
  19 + ng-show="!isEdit && edgeScope === 'tenant' && !isAssignedToCustomer"
  20 + class="md-raised md-primary">{{ 'edge.assign-to-customer' | translate }}</md-button>
  21 +<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})"
  22 + ng-show="!isEdit && (edgeScope === 'customer' || edgeScope === 'tenant') && isAssignedToCustomer"
  23 + class="md-raised md-primary">{{ isPublic ? 'edge.make-private' : 'edge.unassign-from-customer' | translate }}</md-button>
21 24 <md-button ng-click="onDeleteEdge({event: $event})"
22   - ng-if="'edge' | hasGenericPermission:'delete'"
23   - ng-show="!isEdit" class="md-raised md-primary">{{ 'edge.delete' | translate }}</md-button>
  25 + ng-show="!isEdit && edgeScope === 'tenant'"
  26 + class="md-raised md-primary">{{ 'edge.delete' | translate }}</md-button>
24 27
25 28 <div layout="row">
26 29 <md-button ngclipboard data-clipboard-action="copy"
... ... @@ -33,6 +36,15 @@
33 36 </div>
34 37
35 38 <md-content class="md-padding" layout="column">
  39 + <md-input-container class="md-block"
  40 + ng-show="!isEdit && isAssignedToCustomer && !isPublic && edgeScope === 'tenant'">
  41 + <label translate>edge.assignedToCustomer</label>
  42 + <input ng-model="assignedCustomer.title" disabled>
  43 + </md-input-container>
  44 + <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;"
  45 + ng-show="!isEdit && isPublic && (edgeScope === 'customer' || edgeScope === 'tenant')">
  46 + {{ 'edge.edge-public' | translate }}
  47 + </div>
36 48 <fieldset ng-disabled="$root.loading || !isEdit">
37 49 <md-input-container class="md-block">
38 50 <label translate>edge.name</label>
... ... @@ -41,6 +53,17 @@
41 53 <div translate ng-message="required">edge.name-required</div>
42 54 </div>
43 55 </md-input-container>
  56 + <tb-entity-subtype-autocomplete
  57 + ng-disabled="$root.loading || !isEdit"
  58 + tb-required="true"
  59 + the-form="theForm"
  60 + ng-model="edge.type"
  61 + entity-type="types.entityType.edge">
  62 + </tb-entity-subtype-autocomplete>
  63 + <md-input-container class="md-block">
  64 + <label translate>edge.label</label>
  65 + <input name="label" ng-model="edge.label">
  66 + </md-input-container>
44 67 <md-input-container class="md-block">
45 68 <label translate>edge.description</label>
46 69 <textarea ng-model="edge.additionalInfo.description" rows="2"></textarea>
... ...
... ... @@ -20,12 +20,33 @@ import edgeFieldsetTemplate from './edge-fieldset.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 22 /*@ngInject*/
23   -export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, toast, types) {
  23 +export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, toast, types, customerService) {
24 24 var linker = function (scope, element) {
25 25 var template = $templateCache.get(edgeFieldsetTemplate);
26 26 element.html(template);
27 27
28 28 scope.types = types;
  29 + scope.isAssignedToCustomer = false;
  30 + scope.isPublic = false;
  31 + scope.assignedCustomer = null;
  32 +
  33 + scope.$watch('edge', function(newVal) {
  34 + if (newVal) {
  35 + if (scope.edge.customerId && scope.edge.customerId.id !== types.id.nullUid) {
  36 + scope.isAssignedToCustomer = true;
  37 + customerService.getShortCustomerInfo(scope.edge.customerId.id).then(
  38 + function success(customer) {
  39 + scope.assignedCustomer = customer;
  40 + scope.isPublic = customer.isPublic;
  41 + }
  42 + );
  43 + } else {
  44 + scope.isAssignedToCustomer = false;
  45 + scope.isPublic = false;
  46 + scope.assignedCustomer = null;
  47 + }
  48 + }
  49 + });
29 50
30 51 scope.onEdgeIdCopied = function() {
31 52 toast.showSuccess($translate.instant('edge.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
... ... @@ -40,9 +61,11 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD
40 61 scope: {
41 62 edge: '=',
42 63 isEdit: '=',
  64 + edgeScope: '=',
43 65 theForm: '=',
44   - isCreate: '<',
45   - onExportEdge: '&',
  66 + onAssignToCustomer: '&',
  67 + onMakePublic: '&',
  68 + onUnassignFromCustomer: '&',
46 69 onDeleteEdge: '&'
47 70 }
48 71 };
... ...
... ... @@ -20,13 +20,35 @@ import edgesTemplate from './edges.tpl.html';
20 20 /* eslint-enable import/no-unresolved, import/default */
21 21
22 22 /*@ngInject*/
23   -export default function EdgeRoutes($stateProvider) {
24   -
  23 +export default function EdgeRoutes($stateProvider, types) {
25 24 $stateProvider
26 25 .state('home.edges', {
27 26 url: '/edges',
28 27 params: {'topIndex': 0},
29 28 module: 'private',
  29 + auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
  30 + views: {
  31 + "content@home": {
  32 + templateUrl: edgesTemplate,
  33 + controller: 'EdgeController',
  34 + controllerAs: 'vm'
  35 + }
  36 + },
  37 + data: {
  38 + edgesType: 'tenant',
  39 + searchEnabled: true,
  40 + searchByEntitySubtype: true,
  41 + searchEntityType: types.entityType.edge,
  42 + pageTitle: 'edge.edges'
  43 + },
  44 + ncyBreadcrumb: {
  45 + label: '{"icon": "transform", "label": "edge.edges"}'
  46 + }
  47 + })
  48 + .state('home.customers.edges', {
  49 + url: '/:customerId/edges',
  50 + params: {'topIndex': 0},
  51 + module: 'private',
30 52 auth: ['TENANT_ADMIN'],
31 53 views: {
32 54 "content@home": {
... ... @@ -36,11 +58,14 @@ export default function EdgeRoutes($stateProvider) {
36 58 }
37 59 },
38 60 data: {
  61 + edgesType: 'customer',
39 62 searchEnabled: true,
40   - pageTitle: 'edge.edges'
  63 + searchByEntitySubtype: true,
  64 + searchEntityType: types.entityType.edge,
  65 + pageTitle: 'customer.edges'
41 66 },
42 67 ncyBreadcrumb: {
43   - label: '{"icon": "transform", "label": "edge.edges"}'
  68 + label: '{"icon": "transform", "label": "{{ vm.customerEdgesTitle }}", "translate": "false"}'
44 69 }
45 70 });
46 71 }
... ...
... ... @@ -23,22 +23,24 @@
23 23 id="tabs" md-border-bottom flex class="tb-absolute-fill">
24 24 <md-tab label="{{ 'edge.details' | translate }}">
25 25 <tb-edge edge="vm.grid.operatingItem()"
26   - is-edit="vm.grid.detailsConfig.isDetailsEditMode"
27   - the-form="vm.grid.detailsForm"
28   - on-delete-edge="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-edge>
  26 + is-edit="vm.grid.detailsConfig.isDetailsEditMode"
  27 + edge-scope="vm.edgesScope"
  28 + the-form="vm.grid.detailsForm"
  29 + on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
  30 + on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
  31 + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
  32 + on-delete-edge="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-edge>
29 33 </md-tab>
30   - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('edge' | hasGenericPermission:'readAttributes')" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
  34 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
31 35 <tb-attribute-table flex
32   - readonly="!('edge' | hasGenericPermission:'writeAttributes')"
33 36 entity-id="vm.grid.operatingItem().id.id"
34 37 entity-type="{{vm.types.entityType.edge}}"
35 38 entity-name="vm.grid.operatingItem().name"
36 39 default-attribute-scope="{{vm.types.attributesScope.server.value}}">
37 40 </tb-attribute-table>
38 41 </md-tab>
39   - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('edge' | hasGenericPermission:'readTelemetry')" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
  42 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
40 43 <tb-attribute-table flex
41   - readonly="!('edge' | hasGenericPermission:'writeTelemetry')"
42 44 entity-id="vm.grid.operatingItem().id.id"
43 45 entity-type="{{vm.types.entityType.edge}}"
44 46 entity-name="vm.grid.operatingItem().name"
... ... @@ -46,7 +48,7 @@
46 48 disable-attribute-scope-selection="true">
47 49 </tb-attribute-table>
48 50 </md-tab>
49   - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('alarm' | hasGenericPermission:'read')" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
  51 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
50 52 <tb-alarm-table flex entity-type="vm.types.entityType.edge"
51 53 entity-id="vm.grid.operatingItem().id.id">
52 54 </tb-alarm-table>
... ... @@ -65,7 +67,7 @@
65 67 entity-type="{{vm.types.entityType.edge}}">
66 68 </tb-relation-table>
67 69 </md-tab>
68   - <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('auditLog' | hasGenericPermission:'read')" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
  70 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
69 71 <tb-audit-log-table flex entity-type="vm.types.entityType.edge"
70 72 entity-id="vm.grid.operatingItem().id.id"
71 73 audit-log-mode="{{vm.types.auditLogMode.entity}}">
... ...
... ... @@ -13,13 +13,29 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +import uiRouter from 'angular-ui-router';
  17 +import thingsboardGrid from '../components/grid.directive';
  18 +import thingsboardApiUser from '../api/user.service';
  19 +import thingsboardApiEdge from '../api/edge.service';
  20 +import thingsboardApiCustomer from '../api/customer.service';
  21 +
16 22 import EdgeRoutes from './edge.routes';
17 23 import {EdgeController, EdgeCardController} from './edge.controller';
  24 +import AssignEdgeToCustomerController from './assign-to-customer.controller';
  25 +import AddEdgesToCustomerController from './add-edges-to-customer.controller';
18 26 import EdgeDirective from './edge.directive';
19 27
20   -export default angular.module('thingsboard.edge', [])
  28 +export default angular.module('thingsboard.edge', [
  29 + uiRouter,
  30 + thingsboardGrid,
  31 + thingsboardApiUser,
  32 + thingsboardApiEdge,
  33 + thingsboardApiCustomer
  34 +])
21 35 .config(EdgeRoutes)
22 36 .controller('EdgeController', EdgeController)
23 37 .controller('EdgeCardController', EdgeCardController)
  38 + .controller('AssignEdgeToCustomerController', AssignEdgeToCustomerController)
  39 + .controller('AddEdgesToCustomerController', AddEdgesToCustomerController)
24 40 .directive('tbEdge', EdgeDirective)
25 41 .name;
... ...
... ... @@ -22,7 +22,7 @@ import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl
22 22 /* eslint-enable import/no-unresolved, import/default */
23 23
24 24 /*@ngInject*/
25   -export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, entityViewService, types) {
  25 +export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, entityViewService, edgeService, types) {
26 26
27 27 var linker = function (scope, element, attrs, ngModelCtrl) {
28 28 var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
... ... @@ -103,6 +103,8 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
103 103 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
104 104 } else if (scope.entityType == types.entityType.entityView) {
105 105 entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
  106 + } else if (scope.entityType == types.entityType.edge) {
  107 + entitySubtypesPromise = edgeService.getEdgeTypes({ignoreLoading: true});
106 108 }
107 109 if (entitySubtypesPromise) {
108 110 entitySubtypesPromise.then(
... ... @@ -148,6 +150,13 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
148 150 scope.$on('entityViewSaved', function() {
149 151 scope.entitySubtypes = null;
150 152 });
  153 + } else if (scope.entityType == types.entityType.edge) {
  154 + scope.selectEntitySubtypeText = 'edge.select-edge-type';
  155 + scope.entitySubtypeText = 'edge.edge-type';
  156 + scope.entitySubtypeRequiredText = 'edge.edge-type-required';
  157 + scope.$on('edgeSaved', function() {
  158 + scope.entitySubtypes = null;
  159 + });
151 160 }
152 161 }
153 162
... ...
... ... @@ -22,7 +22,7 @@ import entitySubtypeListTemplate from './entity-subtype-list.tpl.html';
22 22 import './entity-subtype-list.scss';
23 23
24 24 /*@ngInject*/
25   -export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService, entityViewService) {
  25 +export default function EntitySubtypeListDirective($compile, $templateCache, $q, $mdUtil, $translate, $filter, types, assetService, deviceService, entityViewService, edgeService) {
26 26
27 27 var linker = function (scope, element, attrs, ngModelCtrl) {
28 28
... ... @@ -53,6 +53,12 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
53 53 scope.secondaryPlaceholder = '+' + $translate.instant('entity-view.entity-view-type');
54 54 scope.noSubtypesMathingText = 'entity-view.no-entity-view-types-matching';
55 55 scope.subtypeListEmptyText = 'entity-view.entity-view-type-list-empty';
  56 + } else if (scope.entityType == types.entityType.edge) {
  57 + scope.placeholder = scope.tbRequired ? $translate.instant('edge.enter-edge-type')
  58 + : $translate.instant('edge.any-edge');
  59 + scope.secondaryPlaceholder = '+' + $translate.instant('edge.edge-type');
  60 + scope.noSubtypesMathingText = 'edge.no-edge-types-matching';
  61 + scope.subtypeListEmptyText = 'edge.edge-type-list-empty';
56 62 }
57 63
58 64 scope.$watch('tbRequired', function () {
... ... @@ -105,6 +111,8 @@ export default function EntitySubtypeListDirective($compile, $templateCache, $q,
105 111 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
106 112 } else if (scope.entityType == types.entityType.entityView) {
107 113 entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
  114 + } else if (scope.entityType == types.entityType.edge) {
  115 + entitySubtypesPromise = edgeService.getEdgeTypes({ignoreLoading: true});
108 116 }
109 117 if (entitySubtypesPromise) {
110 118 entitySubtypesPromise.then(
... ...
... ... @@ -22,7 +22,7 @@ import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
22 22 /* eslint-enable import/no-unresolved, import/default */
23 23
24 24 /*@ngInject*/
25   -export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, entityViewService, types) {
  25 +export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, entityViewService, edgeService, types) {
26 26
27 27 var linker = function (scope, element, attrs, ngModelCtrl) {
28 28 var template = $templateCache.get(entitySubtypeSelectTemplate);
... ... @@ -77,6 +77,8 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
77 77 entitySubtypesPromise = deviceService.getDeviceTypes({ignoreLoading: true});
78 78 } else if (scope.entityType == types.entityType.entityView) {
79 79 entitySubtypesPromise = entityViewService.getEntityViewTypes({ignoreLoading: true});
  80 + } else if (scope.entityType == types.entityType.edge) {
  81 + entitySubtypesPromise = edgeService.getEdgeTypes({ignoreLoading: true});
80 82 }
81 83 if (entitySubtypesPromise) {
82 84 entitySubtypesPromise.then(
... ... @@ -105,6 +107,9 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
105 107 } else if (scope.entityType == types.entityType.entityView) {
106 108 scope.entitySubtypeTitle = 'entity-view.entity-view-type';
107 109 scope.entitySubtypeRequiredText = 'entity-view.entity-view-type-required';
  110 + } else if (scope.entityType == types.entityType.edge) {
  111 + scope.entitySubtypeTitle = 'edge.edge-type';
  112 + scope.entitySubtypeRequiredText = 'edge.edge-type-required';
108 113 }
109 114 scope.entitySubtypes.length = 0;
110 115 if (scope.entitySubtypesList && scope.entitySubtypesList.length) {
... ... @@ -125,6 +130,10 @@ export default function EntitySubtypeSelect($compile, $templateCache, $translate
125 130 scope.$on('entityViewSaved', function() {
126 131 loadSubTypes();
127 132 });
  133 + } else if (scope.entityType == types.entityType.edge) {
  134 + scope.$on('edgeSaved', function() {
  135 + loadSubTypes();
  136 + });
128 137 }
129 138 }
130 139 }
... ...
... ... @@ -319,6 +319,8 @@
319 319 "type-credentials-updated": "Credentials updated",
320 320 "type-assigned-to-customer": "Assigned to Customer",
321 321 "type-unassigned-from-customer": "Unassigned from Customer",
  322 + "type-assigned-to-edge": "Assigned to Edge",
  323 + "type-unassigned-from-edge": "Unassigned from Edge",
322 324 "type-activated": "Activated",
323 325 "type-suspended": "Suspended",
324 326 "type-credentials-read": "Credentials read",
... ... @@ -731,6 +733,9 @@
731 733 "idCopiedMessage": "Edge Id has been copied to clipboard",
732 734 "permissions": "Permissions",
733 735 "edge-required": "Edge required",
  736 + "edge-type": "Edge type",
  737 + "edge-type-required": "Edge type is required.",
  738 + "select-edge-type": "Select edge type"
734 739 },
735 740 "error": {
736 741 "unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
... ...