Commit 00ce0707d71e0741f3d666e5a36e8fc504bd318f
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 | ... | ... |
... | ... | @@ -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, | ... | ... |
... | ... | @@ -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.", | ... | ... |