Commit 3880af82d325baec9c2b9d4a6868f1ff89ce156b

Authored by Volodymyr Babak
1 parent c2ed2cfb

Added Edge entity

Showing 44 changed files with 1983 additions and 7 deletions
  1 +--
  2 +-- Copyright © 2016-2019 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE TABLE IF NOT EXISTS thingsboard.edge (
  18 + id timeuuid,
  19 + tenant_id timeuuid,
  20 + name text,
  21 + search_text text,
  22 + configuration text,
  23 + additional_info text,
  24 + PRIMARY KEY (id, tenant_id)
  25 +);
  26 +
  27 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.edge_by_tenant_and_search_text AS
  28 + SELECT *
  29 + from thingsboard.edge
  30 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  31 + PRIMARY KEY ( tenant_id, search_text, id )
  32 + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
\ No newline at end of file
... ...
  1 +--
  2 +-- Copyright © 2016-2019 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE TABLE IF NOT EXISTS edge (
  18 + id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
  19 + additional_info varchar,
  20 + configuration varchar(10000000),
  21 + name varchar(255),
  22 + search_text varchar(255),
  23 + tenant_id varchar(31)
  24 +);
\ No newline at end of file
... ...
... ... @@ -43,12 +43,14 @@ import org.thingsboard.server.common.data.alarm.AlarmId;
43 43 import org.thingsboard.server.common.data.alarm.AlarmInfo;
44 44 import org.thingsboard.server.common.data.asset.Asset;
45 45 import org.thingsboard.server.common.data.audit.ActionType;
  46 +import org.thingsboard.server.common.data.edge.Edge;
46 47 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
47 48 import org.thingsboard.server.common.data.exception.ThingsboardException;
48 49 import org.thingsboard.server.common.data.id.AssetId;
49 50 import org.thingsboard.server.common.data.id.CustomerId;
50 51 import org.thingsboard.server.common.data.id.DashboardId;
51 52 import org.thingsboard.server.common.data.id.DeviceId;
  53 +import org.thingsboard.server.common.data.id.EdgeId;
52 54 import org.thingsboard.server.common.data.id.EntityId;
53 55 import org.thingsboard.server.common.data.id.EntityIdFactory;
54 56 import org.thingsboard.server.common.data.id.EntityViewId;
... ... @@ -82,6 +84,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
82 84 import org.thingsboard.server.dao.device.ClaimDevicesService;
83 85 import org.thingsboard.server.dao.device.DeviceCredentialsService;
84 86 import org.thingsboard.server.dao.device.DeviceService;
  87 +import org.thingsboard.server.dao.edge.EdgeService;
85 88 import org.thingsboard.server.dao.entityview.EntityViewService;
86 89 import org.thingsboard.server.dao.exception.DataValidationException;
87 90 import org.thingsboard.server.dao.exception.IncorrectParameterException;
... ... @@ -185,6 +188,9 @@ public abstract class BaseController {
185 188 @Autowired
186 189 protected ClaimDevicesService claimDevicesService;
187 190
  191 + @Autowired
  192 + protected EdgeService edgeService;
  193 +
188 194 @Value("${server.log_controller_error_stack_trace}")
189 195 @Getter
190 196 private boolean logControllerErrorStackTrace;
... ... @@ -458,6 +464,18 @@ public abstract class BaseController {
458 464 }
459 465 }
460 466
  467 + Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException {
  468 + try {
  469 + validateId(edgeId, "Incorrect edgeId " + edgeId);
  470 + Edge edge = edgeService.findEdgeById(getTenantId(), edgeId);
  471 + checkNotNull(edge);
  472 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
  473 + return edge;
  474 + } catch (Exception e) {
  475 + throw handleException(e, false);
  476 + }
  477 + }
  478 +
461 479 DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException {
462 480 try {
463 481 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
... ... @@ -513,7 +531,6 @@ public abstract class BaseController {
513 531 return ruleNode;
514 532 }
515 533
516   -
517 534 protected String constructBaseUrl(HttpServletRequest request) {
518 535 String scheme = request.getScheme();
519 536 if (request.getHeader("x-forwarded-proto") != null) {
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import org.springframework.http.HttpStatus;
  19 +import org.springframework.security.access.prepost.PreAuthorize;
  20 +import org.springframework.web.bind.annotation.PathVariable;
  21 +import org.springframework.web.bind.annotation.RequestBody;
  22 +import org.springframework.web.bind.annotation.RequestMapping;
  23 +import org.springframework.web.bind.annotation.RequestMethod;
  24 +import org.springframework.web.bind.annotation.RequestParam;
  25 +import org.springframework.web.bind.annotation.ResponseBody;
  26 +import org.springframework.web.bind.annotation.ResponseStatus;
  27 +import org.springframework.web.bind.annotation.RestController;
  28 +import org.thingsboard.server.common.data.EntityType;
  29 +import org.thingsboard.server.common.data.audit.ActionType;
  30 +import org.thingsboard.server.common.data.edge.Edge;
  31 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  32 +import org.thingsboard.server.common.data.id.EdgeId;
  33 +import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.page.TextPageData;
  35 +import org.thingsboard.server.common.data.page.TextPageLink;
  36 +import org.thingsboard.server.service.security.model.SecurityUser;
  37 +import org.thingsboard.server.service.security.permission.Operation;
  38 +import org.thingsboard.server.service.security.permission.Resource;
  39 +
  40 +import java.util.ArrayList;
  41 +import java.util.List;
  42 +import java.util.stream.Collectors;
  43 +
  44 +@RestController
  45 +@RequestMapping("/api")
  46 +public class EdgeController extends BaseController {
  47 +
  48 + private static final String EDGE_ID = "edgeId";
  49 +
  50 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  51 + @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
  52 + @ResponseBody
  53 + public Edge getEdgeById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  54 + checkParameter(EDGE_ID, strEdgeId);
  55 + try {
  56 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  57 + return checkEdgeId(edgeId, Operation.READ);
  58 + } catch (Exception e) {
  59 + throw handleException(e);
  60 + }
  61 + }
  62 +
  63 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  64 + @RequestMapping(value = "/edge", method = RequestMethod.POST)
  65 + @ResponseBody
  66 + public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException {
  67 + try {
  68 + edge.setTenantId(getCurrentUser().getTenantId());
  69 + boolean created = edge.getId() == null;
  70 +
  71 + Operation operation = created ? Operation.CREATE : Operation.WRITE;
  72 +
  73 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
  74 + edge.getId(), edge);
  75 +
  76 + Edge result = checkNotNull(edgeService.saveEdge(edge));
  77 +
  78 + logEntityAction(result.getId(), result, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
  79 + return result;
  80 + } catch (Exception e) {
  81 + logEntityAction(emptyId(EntityType.EDGE), edge,
  82 + null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  83 + throw handleException(e);
  84 + }
  85 + }
  86 +
  87 +
  88 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  89 + @RequestMapping(value = "/edges", params = {"limit"}, method = RequestMethod.GET)
  90 + @ResponseBody
  91 + public TextPageData<Edge> getEdges(
  92 + @RequestParam int limit,
  93 + @RequestParam(required = false) String textSearch,
  94 + @RequestParam(required = false) String idOffset,
  95 + @RequestParam(required = false) String textOffset) throws ThingsboardException {
  96 + try {
  97 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ);
  98 + TenantId tenantId = getCurrentUser().getTenantId();
  99 + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
  100 + return checkNotNull(edgeService.findTenantEdges(tenantId, pageLink));
  101 + } catch (Exception e) {
  102 + throw handleException(e);
  103 + }
  104 + }
  105 +
  106 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  107 + @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE)
  108 + @ResponseStatus(value = HttpStatus.OK)
  109 + public void deleteEdge(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  110 + checkParameter(EDGE_ID, strEdgeId);
  111 + try {
  112 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  113 + Edge edge = checkEdgeId(edgeId, Operation.DELETE);
  114 + edgeService.deleteEdge(getTenantId(), edgeId);
  115 +
  116 + logEntityAction(edgeId, edge,
  117 + null,
  118 + ActionType.DELETED, null, strEdgeId);
  119 +
  120 + } catch (Exception e) {
  121 +
  122 + logEntityAction(emptyId(EntityType.EDGE),
  123 + null,
  124 + null,
  125 + ActionType.DELETED, e, strEdgeId);
  126 +
  127 + throw handleException(e);
  128 + }
  129 + }
  130 +
  131 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  132 + @RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
  133 + @ResponseBody
  134 + public List<Edge> getEdgesByIds(
  135 + @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException {
  136 + checkArrayParameter("edgeIds", strEdgeIds);
  137 + try {
  138 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ);
  139 + SecurityUser user = getCurrentUser();
  140 + TenantId tenantId = user.getTenantId();
  141 + List<EdgeId> edgeIds = new ArrayList<>();
  142 + for (String strEdgeId : strEdgeIds) {
  143 + edgeIds.add(new EdgeId(toUUID(strEdgeId)));
  144 + }
  145 + List<Edge> edges = checkNotNull(edgeService.findEdgesByIdsAsync(tenantId, edgeIds).get());
  146 + return filterEdgesByReadPermission(edges);
  147 + } catch (Exception e) {
  148 + throw handleException(e);
  149 + }
  150 + }
  151 +
  152 + private List<Edge> filterEdgesByReadPermission(List<Edge> edges) {
  153 + return edges.stream().filter(edge -> {
  154 + try {
  155 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ, edge.getId(), edge);
  156 + return true;
  157 + } catch (ThingsboardException e) {
  158 + return false;
  159 + }
  160 + }).collect(Collectors.toList());
  161 + }
  162 +
  163 +}
... ...
... ... @@ -119,6 +119,11 @@ public class ThingsboardInstallService {
119 119 case "2.4.0":
120 120 log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ...");
121 121
  122 + case "2.4.2":
  123 + log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.x ...");
  124 +
  125 + databaseUpgradeService.upgradeDatabase("2.4.2");
  126 +
122 127 log.info("Updating system data...");
123 128
124 129 systemDataLoaderService.deleteSystemWidgetBundle("charts");
... ...
... ... @@ -267,6 +267,14 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
267 267 } catch (InvalidQueryException e) {}
268 268 log.info("Schema updated.");
269 269 break;
  270 + case "2.4.2":
  271 +
  272 + log.info("Updating schema ...");
  273 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.x", SCHEMA_UPDATE_CQL);
  274 + loadCql(schemaUpdateFile);
  275 + log.info("Schema updated.");
  276 +
  277 + break;
270 278 default:
271 279 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
272 280 }
... ...
... ... @@ -176,6 +176,14 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
176 176 log.info("Schema updated.");
177 177 }
178 178 break;
  179 + case "2.4.2":
  180 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  181 + log.info("Updating schema ...");
  182 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.x", SCHEMA_UPDATE_SQL);
  183 + loadSql(schemaUpdateFile, conn);
  184 + log.info("Schema updated.");
  185 + }
  186 + break;
179 187 default:
180 188 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
181 189 }
... ...
... ... @@ -31,7 +31,8 @@ public enum Resource {
31 31 RULE_CHAIN(EntityType.RULE_CHAIN),
32 32 USER(EntityType.USER),
33 33 WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE),
34   - WIDGET_TYPE(EntityType.WIDGET_TYPE);
  34 + WIDGET_TYPE(EntityType.WIDGET_TYPE),
  35 + EDGE(EntityType.EDGE);
35 36
36 37 private final EntityType entityType;
37 38
... ...
... ... @@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
42 42 put(Resource.USER, userPermissionChecker);
43 43 put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
44 44 put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
  45 + put(Resource.EDGE, tenantEntityPermissionChecker);
45 46 }
46 47
47 48 public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.apache.commons.lang3.RandomStringUtils;
  20 +import org.junit.After;
  21 +import org.junit.Assert;
  22 +import org.junit.Before;
  23 +import org.junit.Test;
  24 +import org.thingsboard.server.common.data.Tenant;
  25 +import org.thingsboard.server.common.data.User;
  26 +import org.thingsboard.server.common.data.edge.Edge;
  27 +import org.thingsboard.server.common.data.page.TextPageData;
  28 +import org.thingsboard.server.common.data.page.TextPageLink;
  29 +import org.thingsboard.server.common.data.security.Authority;
  30 +
  31 +import java.util.ArrayList;
  32 +import java.util.Collections;
  33 +import java.util.List;
  34 +
  35 +import static org.hamcrest.Matchers.containsString;
  36 +import static org.junit.Assert.assertEquals;
  37 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  38 +
  39 +public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
  40 +
  41 + private IdComparator<Edge> idComparator;
  42 + private Tenant savedTenant;
  43 + private User tenantAdmin;
  44 +
  45 + @Before
  46 + public void beforeTest() throws Exception {
  47 + loginSysAdmin();
  48 + idComparator = new IdComparator<>();
  49 +
  50 + savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class);
  51 + Assert.assertNotNull(savedTenant);
  52 +
  53 + tenantAdmin = new User();
  54 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  55 + tenantAdmin.setTenantId(savedTenant.getId());
  56 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  57 + tenantAdmin.setFirstName("Joe");
  58 + tenantAdmin.setLastName("Downs");
  59 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  60 +
  61 + }
  62 +
  63 + @After
  64 + public void afterTest() throws Exception {
  65 + loginSysAdmin();
  66 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  67 + .andExpect(status().isOk());
  68 + }
  69 +
  70 + @Test
  71 + public void testFindEdgeById() throws Exception {
  72 + Edge savedEdge = getNewSavedEdge("Test edge");
  73 + Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  74 + Assert.assertNotNull(foundEdge);
  75 + assertEquals(savedEdge, foundEdge);
  76 + }
  77 +
  78 + @Test
  79 + public void testSaveEdge() throws Exception {
  80 + Edge savedEdge = getNewSavedEdge("Test edge");
  81 +
  82 + Assert.assertNotNull(savedEdge);
  83 + Assert.assertNotNull(savedEdge.getId());
  84 + Assert.assertTrue(savedEdge.getCreatedTime() > 0);
  85 + assertEquals(savedTenant.getId(), savedEdge.getTenantId());
  86 +
  87 + savedEdge.setName("New test edge");
  88 + doPost("/api/edge", savedEdge, Edge.class);
  89 + Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  90 +
  91 + assertEquals(foundEdge.getName(), savedEdge.getName());
  92 + }
  93 +
  94 + @Test
  95 + public void testDeleteEdge() throws Exception {
  96 + Edge edge = getNewSavedEdge("Test edge");
  97 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  98 +
  99 + doDelete("/api/edge/" + savedEdge.getId().getId().toString())
  100 + .andExpect(status().isOk());
  101 +
  102 + doGet("/api/edge/" + savedEdge.getId().getId().toString())
  103 + .andExpect(status().isNotFound());
  104 + }
  105 +
  106 + @Test
  107 + public void testSaveEdgeWithEmptyName() throws Exception {
  108 + Edge edge = new Edge();
  109 + doPost("/api/edge", edge)
  110 + .andExpect(status().isBadRequest())
  111 + .andExpect(statusReason(containsString("Edge name should be specified!")));
  112 + }
  113 +
  114 + @Test
  115 + public void testGetEdges() throws Exception {
  116 +
  117 + List<Edge> edges = new ArrayList<>();
  118 + for (int i = 0; i < 178; i++) {
  119 + edges.add(getNewSavedEdge("Test edge " + i));
  120 + }
  121 + List<Edge> loadedEdges = loadListOf(new TextPageLink(23), "/api/edges?");
  122 +
  123 + Collections.sort(edges, idComparator);
  124 + Collections.sort(loadedEdges, idComparator);
  125 +
  126 + assertEquals(edges, loadedEdges);
  127 + }
  128 +
  129 + @Test
  130 + public void testGetEdgesByName() throws Exception {
  131 + String name1 = "Entity edge1";
  132 + List<Edge> namesOfEdge1 = fillListOf(143, name1);
  133 + List<Edge> loadedNamesOfEdge1 = loadListOf(new TextPageLink(15, name1), "/api/edges?");
  134 + Collections.sort(namesOfEdge1, idComparator);
  135 + Collections.sort(loadedNamesOfEdge1, idComparator);
  136 + assertEquals(namesOfEdge1, loadedNamesOfEdge1);
  137 +
  138 + String name2 = "Entity edge2";
  139 + List<Edge> namesOfEdge2 = fillListOf(75, name2);
  140 + List<Edge> loadedNamesOfEdge2 = loadListOf(new TextPageLink(4, name2), "/api/edges?");
  141 + Collections.sort(namesOfEdge2, idComparator);
  142 + Collections.sort(loadedNamesOfEdge2, idComparator);
  143 + assertEquals(namesOfEdge2, loadedNamesOfEdge2);
  144 +
  145 + for (Edge edge : loadedNamesOfEdge1) {
  146 + doDelete("/api/edge/" + edge.getId().getId().toString()).andExpect(status().isOk());
  147 + }
  148 + TextPageData<Edge> pageData = doGetTypedWithPageLink("/api/edges?",
  149 + new TypeReference<TextPageData<Edge>>() {
  150 + }, new TextPageLink(4, name1));
  151 + Assert.assertFalse(pageData.hasNext());
  152 + assertEquals(0, pageData.getData().size());
  153 +
  154 + for (Edge edge : loadedNamesOfEdge2) {
  155 + doDelete("/api/edge/" + edge.getId().getId().toString()).andExpect(status().isOk());
  156 + }
  157 + pageData = doGetTypedWithPageLink("/api/edges?", new TypeReference<TextPageData<Edge>>() {
  158 + }, new TextPageLink(4, name2));
  159 + Assert.assertFalse(pageData.hasNext());
  160 + assertEquals(0, pageData.getData().size());
  161 + }
  162 +
  163 + private Edge getNewSavedEdge(String name) throws Exception {
  164 + Edge edge = createEdge(name);
  165 + return doPost("/api/edge", edge, Edge.class);
  166 + }
  167 +
  168 + private Edge createEdge(String name) {
  169 + Edge edge = new Edge();
  170 + edge.setTenantId(savedTenant.getId());
  171 + edge.setName(name);
  172 + return edge;
  173 + }
  174 +
  175 + private Tenant getNewTenant(String title) {
  176 + Tenant tenant = new Tenant();
  177 + tenant.setTitle(title);
  178 + return tenant;
  179 + }
  180 +
  181 + private List<Edge> fillListOf(int limit, String partOfName) throws Exception {
  182 + List<Edge> edgeNames = new ArrayList<>();
  183 + for (int i = 0; i < limit; i++) {
  184 + String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15);
  185 + fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase();
  186 + Edge edge = getNewSavedEdge(fullName);
  187 + edgeNames.add(doPost("/api/edge", edge, Edge.class));
  188 + }
  189 + return edgeNames;
  190 + }
  191 +
  192 + private List<Edge> loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception {
  193 + List<Edge> loadedItems = new ArrayList<>();
  194 + TextPageData<Edge> pageData;
  195 + do {
  196 + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<Edge>>() {
  197 + }, pageLink);
  198 + loadedItems.addAll(pageData.getData());
  199 + if (pageData.hasNext()) {
  200 + pageLink = pageData.getNextPageLink();
  201 + }
  202 + } while (pageData.hasNext());
  203 +
  204 + return loadedItems;
  205 + }
  206 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.nosql;
  17 +
  18 +import org.thingsboard.server.controller.BaseEdgeControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  20 +
  21 +@DaoNoSqlTest
  22 +public class EdgeControllerNoSqlTest extends BaseEdgeControllerTest {
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.sql;
  17 +
  18 +import org.thingsboard.server.controller.BaseEdgeControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class EdgeControllerSqlTest extends BaseEdgeControllerTest {
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.edge;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.edge.Edge;
  20 +import org.thingsboard.server.common.data.id.EdgeId;
  21 +import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.data.page.TextPageData;
  23 +import org.thingsboard.server.common.data.page.TextPageLink;
  24 +
  25 +import java.util.List;
  26 +
  27 +public interface EdgeService {
  28 +
  29 + Edge saveEdge(Edge edge);
  30 +
  31 + Edge findEdgeById(TenantId tenantId, EdgeId edgeId);
  32 +
  33 + ListenableFuture<Edge> findEdgeByIdAsync(TenantId tenantId, EdgeId edgeId);
  34 +
  35 + ListenableFuture<List<Edge>> findEdgesByIdsAsync(TenantId tenantId, List<EdgeId> edgeIds);
  36 +
  37 + List<Edge> findAllEdges(TenantId tenantId);
  38 +
  39 + TextPageData<Edge> findTenantEdges(TenantId tenantId, TextPageLink pageLink);
  40 +
  41 + void deleteEdge(TenantId tenantId, EdgeId edgeId);
  42 +
  43 + void deleteEdgesByTenantId(TenantId tenantId);
  44 +
  45 +}
... ...
... ... @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 19 * @author Andrew Shvayka
20 20 */
21 21 public enum EntityType {
22   - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE
  22 + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, EDGE
23 23 }
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.edge;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.Getter;
  21 +import lombok.Setter;
  22 +import lombok.ToString;
  23 +import org.thingsboard.server.common.data.HasCustomerId;
  24 +import org.thingsboard.server.common.data.HasName;
  25 +import org.thingsboard.server.common.data.HasTenantId;
  26 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  27 +import org.thingsboard.server.common.data.id.CustomerId;
  28 +import org.thingsboard.server.common.data.id.EdgeId;
  29 +import org.thingsboard.server.common.data.id.TenantId;
  30 +
  31 +@EqualsAndHashCode(callSuper = true)
  32 +@ToString
  33 +@Getter
  34 +@Setter
  35 +public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasName, HasTenantId, HasCustomerId {
  36 +
  37 + private static final long serialVersionUID = 4934987555236873728L;
  38 +
  39 + private TenantId tenantId;
  40 + private CustomerId customerId;
  41 + private String name;
  42 + private transient JsonNode configuration;
  43 + private transient JsonNode additionalInfo;
  44 +
  45 + public Edge() {
  46 + super();
  47 + }
  48 +
  49 + public Edge(EdgeId id) {
  50 + super(id);
  51 + }
  52 +
  53 + public Edge(Edge edge) {
  54 + super(edge);
  55 + this.tenantId = edge.getTenantId();
  56 + this.name = edge.getName();
  57 + this.configuration = edge.getConfiguration();
  58 + this.additionalInfo = edge.getAdditionalInfo();
  59 + }
  60 +
  61 + @Override
  62 + public String getSearchText() {
  63 + return getName();
  64 + }
  65 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonIgnore;
  20 +import com.fasterxml.jackson.annotation.JsonProperty;
  21 +import org.thingsboard.server.common.data.EntityType;
  22 +
  23 +import java.util.UUID;
  24 +
  25 +public class EdgeId extends UUIDBased implements EntityId {
  26 +
  27 + private static final long serialVersionUID = 1L;
  28 +
  29 + @JsonCreator
  30 + public EdgeId(@JsonProperty("id") UUID id) {
  31 + super(id);
  32 + }
  33 +
  34 + public static EdgeId fromString(String integrationId) {
  35 + return new EdgeId(UUID.fromString(integrationId));
  36 + }
  37 +
  38 + @JsonIgnore
  39 + @Override
  40 + public EntityType getEntityType() {
  41 + return EntityType.EDGE;
  42 + }
  43 +}
... ...
... ... @@ -63,6 +63,8 @@ public class EntityIdFactory {
63 63 return new WidgetsBundleId(uuid);
64 64 case WIDGET_TYPE:
65 65 return new WidgetTypeId(uuid);
  66 + case EDGE:
  67 + return new EdgeId(uuid);
66 68 }
67 69 throw new IllegalArgumentException("EntityType " + type + " is not supported!");
68 70 }
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.edge;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.stereotype.Service;
  22 +import org.springframework.util.StringUtils;
  23 +import org.thingsboard.server.common.data.Tenant;
  24 +import org.thingsboard.server.common.data.edge.Edge;
  25 +import org.thingsboard.server.common.data.id.EdgeId;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.page.TextPageData;
  28 +import org.thingsboard.server.common.data.page.TextPageLink;
  29 +import org.thingsboard.server.dao.entity.AbstractEntityService;
  30 +import org.thingsboard.server.dao.exception.DataValidationException;
  31 +import org.thingsboard.server.dao.service.DataValidator;
  32 +import org.thingsboard.server.dao.service.PaginatedRemover;
  33 +import org.thingsboard.server.dao.tenant.TenantDao;
  34 +
  35 +import java.util.List;
  36 +
  37 +import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
  38 +import static org.thingsboard.server.dao.service.Validator.validateId;
  39 +import static org.thingsboard.server.dao.service.Validator.validateIds;
  40 +import static org.thingsboard.server.dao.service.Validator.validatePageLink;
  41 +
  42 +@Service
  43 +@Slf4j
  44 +public class BaseEdgeService extends AbstractEntityService implements EdgeService {
  45 +
  46 + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  47 + public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
  48 + public static final String INCORRECT_EDGE_ID = "Incorrect edgeId ";
  49 +
  50 + @Autowired
  51 + private EdgeDao edgeDao;
  52 +
  53 + @Autowired
  54 + private TenantDao tenantDao;
  55 +
  56 + @Override
  57 + public Edge saveEdge(Edge edge) {
  58 + log.trace("Executing saveEdge [{}]", edge);
  59 + edgeValidator.validate(edge, Edge::getTenantId);
  60 + return edgeDao.save(edge.getTenantId(), edge);
  61 + }
  62 +
  63 + @Override
  64 + public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) {
  65 + log.trace("Executing findEdgeById [{}]", edgeId);
  66 + validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
  67 + return edgeDao.findById(tenantId, edgeId.getId());
  68 + }
  69 +
  70 + @Override
  71 + public ListenableFuture<Edge> findEdgeByIdAsync(TenantId tenantId, EdgeId edgeId) {
  72 + log.trace("Executing findEdgeByIdAsync [{}]", edgeId);
  73 + validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
  74 + return edgeDao.findByIdAsync(tenantId, edgeId.getId());
  75 + }
  76 +
  77 + @Override
  78 + public ListenableFuture<List<Edge>> findEdgesByIdsAsync(TenantId tenantId, List<EdgeId> edgeIds) {
  79 + log.trace("Executing findEdgesByIdsAsync, tenantId [{}], edgeIds [{}]", tenantId, edgeIds);
  80 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  81 + validateIds(edgeIds, "Incorrect edgeIds " + edgeIds);
  82 + return edgeDao.findEdgesByTenantIdAndIdsAsync(tenantId.getId(), toUUIDs(edgeIds));
  83 + }
  84 +
  85 + @Override
  86 + public List<Edge> findAllEdges(TenantId tenantId) {
  87 + log.trace("Executing findAllEdges");
  88 + return edgeDao.find(tenantId);
  89 + }
  90 +
  91 + @Override
  92 + public TextPageData<Edge> findTenantEdges(TenantId tenantId, TextPageLink pageLink) {
  93 + log.trace("Executing findTenantEdges, tenantId [{}], pageLink [{}]", tenantId, pageLink);
  94 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  95 + validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
  96 + List<Edge> edges = edgeDao.findByTenantIdAndPageLink(tenantId.getId(), pageLink);
  97 + return new TextPageData<>(edges, pageLink);
  98 + }
  99 +
  100 + @Override
  101 + public void deleteEdge(TenantId tenantId, EdgeId edgeId) {
  102 + log.trace("Executing deleteEdge [{}]", edgeId);
  103 + validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
  104 + deleteEntityRelations(tenantId, edgeId);
  105 + edgeDao.removeById(tenantId, edgeId.getId());
  106 + }
  107 +
  108 + @Override
  109 + public void deleteEdgesByTenantId(TenantId tenantId) {
  110 + log.trace("Executing deleteEdgesByTenantId, tenantId [{}]", tenantId);
  111 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  112 + tenantEdgesRemover.removeEntities(tenantId, tenantId);
  113 + }
  114 +
  115 + private DataValidator<Edge> edgeValidator =
  116 + new DataValidator<Edge>() {
  117 +
  118 + @Override
  119 + protected void validateCreate(TenantId tenantId, Edge edge) {
  120 + }
  121 +
  122 + @Override
  123 + protected void validateUpdate(TenantId tenantId, Edge edge) {
  124 + }
  125 +
  126 + @Override
  127 + protected void validateDataImpl(TenantId tenantId, Edge edge) {
  128 + if (StringUtils.isEmpty(edge.getName())) {
  129 + throw new DataValidationException("Edge name should be specified!");
  130 + }
  131 + if (edge.getTenantId() == null || edge.getTenantId().isNullUid()) {
  132 + throw new DataValidationException("Edge should be assigned to tenant!");
  133 + } else {
  134 + Tenant tenant = tenantDao.findById(tenantId, edge.getTenantId().getId());
  135 + if (tenant == null) {
  136 + throw new DataValidationException("Edge is referencing to non-existent tenant!");
  137 + }
  138 + }
  139 + }
  140 + };
  141 +
  142 + private PaginatedRemover<TenantId, Edge> tenantEdgesRemover =
  143 + new PaginatedRemover<TenantId, Edge>() {
  144 +
  145 + @Override
  146 + protected List<Edge> findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) {
  147 + return edgeDao.findByTenantIdAndPageLink(id.getId(), pageLink);
  148 + }
  149 +
  150 + @Override
  151 + protected void removeEntity(TenantId tenantId, Edge entity) {
  152 + deleteEdge(tenantId, new EdgeId(entity.getId().getId()));
  153 + }
  154 + };
  155 +
  156 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.edge;
  17 +
  18 +import com.datastax.driver.core.querybuilder.Select;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.common.data.edge.Edge;
  23 +import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.common.data.page.TextPageLink;
  25 +import org.thingsboard.server.dao.DaoUtil;
  26 +import org.thingsboard.server.dao.model.nosql.EdgeEntity;
  27 +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
  28 +import org.thingsboard.server.dao.util.NoSqlDao;
  29 +
  30 +import java.util.Collections;
  31 +import java.util.List;
  32 +import java.util.UUID;
  33 +
  34 +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
  35 +import static com.datastax.driver.core.querybuilder.QueryBuilder.in;
  36 +import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
  37 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
  38 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
  39 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
  41 +
  42 +@Component
  43 +@Slf4j
  44 +@NoSqlDao
  45 +public class CassandraEdgeDao extends CassandraAbstractSearchTextDao<EdgeEntity, Edge> implements EdgeDao {
  46 +
  47 + @Override
  48 + protected Class<EdgeEntity> getColumnFamilyClass() {
  49 + return EdgeEntity.class;
  50 + }
  51 +
  52 + @Override
  53 + protected String getColumnFamilyName() {
  54 + return EDGE_COLUMN_FAMILY_NAME;
  55 + }
  56 +
  57 + @Override
  58 + public List<Edge> findByTenantIdAndPageLink(UUID tenantId, TextPageLink pageLink) {
  59 + log.debug("Try to find edges by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
  60 + List<EdgeEntity> edgeEntities = findPageWithTextSearch(new TenantId(tenantId), EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
  61 + Collections.singletonList(eq(EDGE_TENANT_ID_PROPERTY, tenantId)), pageLink);
  62 +
  63 + log.trace("Found edges [{}] by tenantId [{}] and pageLink [{}]", edgeEntities, tenantId, pageLink);
  64 + return DaoUtil.convertDataList(edgeEntities);
  65 + }
  66 +
  67 + @Override
  68 + public ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> edgeIds) {
  69 + log.debug("Try to find edges by tenantId [{}] and edge Ids [{}]", tenantId, edgeIds);
  70 + Select select = select().from(getColumnFamilyName());
  71 + Select.Where query = select.where();
  72 + query.and(eq(EDGE_TENANT_ID_PROPERTY, tenantId));
  73 + query.and(in(ID_PROPERTY, edgeIds));
  74 + return findListByStatementAsync(new TenantId(tenantId), query);
  75 + }
  76 +
  77 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.edge;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.edge.Edge;
  20 +import org.thingsboard.server.common.data.page.TextPageLink;
  21 +import org.thingsboard.server.dao.Dao;
  22 +
  23 +import java.util.List;
  24 +import java.util.UUID;
  25 +
  26 +/**
  27 + * The Interface EdgeDao.
  28 + *
  29 + */
  30 +public interface EdgeDao extends Dao<Edge> {
  31 +
  32 + /**
  33 + * Find edges by tenantId and page link.
  34 + *
  35 + * @param tenantId the tenantId
  36 + * @param pageLink the page link
  37 + * @return the list of edge objects
  38 + */
  39 + List<Edge> findByTenantIdAndPageLink(UUID tenantId, TextPageLink pageLink);
  40 +
  41 + /**
  42 + * Find edges by tenantId and edge Ids.
  43 + *
  44 + * @param tenantId the tenantId
  45 + * @param edgeIds the edge Ids
  46 + * @return the list of edge objects
  47 + */
  48 + ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> edgeIds);
  49 +
  50 +
  51 +}
... ...
... ... @@ -348,6 +348,18 @@ public class ModelConstants {
348 348 public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration";
349 349
350 350 /**
  351 + * Cassandra edge constants.
  352 + */
  353 + public static final String EDGE_COLUMN_FAMILY_NAME = "edge";
  354 + public static final String EDGE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
  355 + public static final String EDGE_NAME_PROPERTY = "name";
  356 + public static final String EDGE_CONFIGURATION_PROPERTY = "configuration";
  357 + public static final String EDGE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
  358 +
  359 + public static final String EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_tenant_and_search_text";
  360 +
  361 +
  362 + /**
351 363 * Cassandra attributes and timeseries constants.
352 364 */
353 365 public static final String ATTRIBUTES_KV_CF = "attributes_kv_cf";
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.nosql;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.datastax.driver.mapping.annotations.ClusteringColumn;
  20 +import com.datastax.driver.mapping.annotations.Column;
  21 +import com.datastax.driver.mapping.annotations.PartitionKey;
  22 +import com.datastax.driver.mapping.annotations.Table;
  23 +import com.fasterxml.jackson.databind.JsonNode;
  24 +import lombok.Data;
  25 +import org.thingsboard.server.common.data.edge.Edge;
  26 +import org.thingsboard.server.common.data.id.EdgeId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.dao.model.SearchTextEntity;
  29 +import org.thingsboard.server.dao.model.type.JsonCodec;
  30 +
  31 +import java.util.UUID;
  32 +
  33 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ADDITIONAL_INFO_PROPERTY;
  34 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
  35 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CONFIGURATION_PROPERTY;
  36 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
  37 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
  38 +import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
  39 +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
  40 +
  41 +@Data
  42 +@Table(name = EDGE_COLUMN_FAMILY_NAME)
  43 +public class EdgeEntity implements SearchTextEntity<Edge> {
  44 +
  45 + @PartitionKey
  46 + @Column(name = ID_PROPERTY)
  47 + private UUID id;
  48 +
  49 + @ClusteringColumn
  50 + @Column(name = EDGE_TENANT_ID_PROPERTY)
  51 + private UUID tenantId;
  52 +
  53 + @Column(name = EDGE_NAME_PROPERTY)
  54 + private String name;
  55 +
  56 + @Column(name = SEARCH_TEXT_PROPERTY)
  57 + private String searchText;
  58 +
  59 + @Column(name = EDGE_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
  60 + private JsonNode configuration;
  61 +
  62 + @Column(name = EDGE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
  63 + private JsonNode additionalInfo;
  64 +
  65 + public EdgeEntity() {
  66 + super();
  67 + }
  68 +
  69 + public EdgeEntity(Edge edge) {
  70 + if (edge.getId() != null) {
  71 + this.id = edge.getId().getId();
  72 + }
  73 + if (edge.getTenantId() != null) {
  74 + this.tenantId = edge.getTenantId().getId();
  75 + }
  76 + this.name = edge.getName();
  77 + this.configuration = edge.getConfiguration();
  78 + this.additionalInfo = edge.getAdditionalInfo();
  79 + }
  80 +
  81 + @Override
  82 + public String getSearchTextSource() {
  83 + return getName();
  84 + }
  85 +
  86 + @Override
  87 + public Edge toData() {
  88 + Edge edge = new Edge(new EdgeId(id));
  89 + edge.setCreatedTime(UUIDs.unixTimestamp(id));
  90 + if (tenantId != null) {
  91 + edge.setTenantId(new TenantId(tenantId));
  92 + }
  93 + edge.setName(name);
  94 + edge.setConfiguration(configuration);
  95 + edge.setAdditionalInfo(additionalInfo);
  96 + return edge;
  97 + }
  98 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import org.hibernate.annotations.Type;
  23 +import org.hibernate.annotations.TypeDef;
  24 +import org.thingsboard.server.common.data.UUIDConverter;
  25 +import org.thingsboard.server.common.data.edge.Edge;
  26 +import org.thingsboard.server.common.data.id.EdgeId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  29 +import org.thingsboard.server.dao.model.ModelConstants;
  30 +import org.thingsboard.server.dao.model.SearchTextEntity;
  31 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  32 +
  33 +import javax.persistence.Column;
  34 +import javax.persistence.Entity;
  35 +import javax.persistence.Table;
  36 +
  37 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
  38 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
  39 +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
  41 +
  42 +@Data
  43 +@EqualsAndHashCode(callSuper = true)
  44 +@Entity
  45 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  46 +@Table(name = EDGE_COLUMN_FAMILY_NAME)
  47 +public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<Edge> {
  48 +
  49 + @Column(name = EDGE_TENANT_ID_PROPERTY)
  50 + private String tenantId;
  51 +
  52 + @Column(name = EDGE_NAME_PROPERTY)
  53 + private String name;
  54 +
  55 + @Column(name = SEARCH_TEXT_PROPERTY)
  56 + private String searchText;
  57 +
  58 + @Type(type = "json")
  59 + @Column(name = ModelConstants.EDGE_CONFIGURATION_PROPERTY)
  60 + private JsonNode configuration;
  61 +
  62 + @Type(type = "json")
  63 + @Column(name = ModelConstants.EDGE_ADDITIONAL_INFO_PROPERTY)
  64 + private JsonNode additionalInfo;
  65 +
  66 + public EdgeEntity() {
  67 + super();
  68 + }
  69 +
  70 + public EdgeEntity(Edge edge) {
  71 + if (edge.getId() != null) {
  72 + this.setId(edge.getId().getId());
  73 + }
  74 + if (edge.getTenantId() != null) {
  75 + this.tenantId = UUIDConverter.fromTimeUUID(edge.getTenantId().getId());
  76 + }
  77 + this.name = edge.getName();
  78 + this.configuration = edge.getConfiguration();
  79 + this.additionalInfo = edge.getAdditionalInfo();
  80 + }
  81 +
  82 + public String getSearchText() {
  83 + return searchText;
  84 + }
  85 +
  86 + @Override
  87 + public String getSearchTextSource() {
  88 + return name;
  89 + }
  90 +
  91 + @Override
  92 + public void setSearchText(String searchText) {
  93 + this.searchText = searchText;
  94 + }
  95 +
  96 + @Override
  97 + public Edge toData() {
  98 + Edge edge = new Edge(new EdgeId(UUIDConverter.fromString(id)));
  99 + edge.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id)));
  100 + if (tenantId != null) {
  101 + edge.setTenantId(new TenantId(UUIDConverter.fromString(tenantId)));
  102 + }
  103 + edge.setName(name);
  104 + edge.setConfiguration(configuration);
  105 + edge.setAdditionalInfo(additionalInfo);
  106 + return edge;
  107 + }
  108 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.edge;
  17 +
  18 +import org.springframework.data.domain.Pageable;
  19 +import org.springframework.data.jpa.repository.Query;
  20 +import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.data.repository.query.Param;
  22 +import org.thingsboard.server.dao.model.sql.EdgeEntity;
  23 +import org.thingsboard.server.dao.util.SqlDao;
  24 +
  25 +import java.util.List;
  26 +
  27 +@SqlDao
  28 +public interface EdgeRepository extends CrudRepository<EdgeEntity, String> {
  29 +
  30 + @Query("SELECT a FROM EdgeEntity a WHERE a.tenantId = :tenantId " +
  31 + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
  32 + "AND a.id > :idOffset ORDER BY a.id")
  33 + List<EdgeEntity> findByTenantIdAndPageLink(@Param("tenantId") String tenantId,
  34 + @Param("textSearch") String textSearch,
  35 + @Param("idOffset") String idOffset,
  36 + Pageable pageable);
  37 +
  38 + List<EdgeEntity> findEdgesByTenantIdAndIdIn(String tenantId, List<String> edgeIds);
  39 +
  40 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.sql.edge;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.data.domain.PageRequest;
  21 +import org.springframework.data.repository.CrudRepository;
  22 +import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.common.data.UUIDConverter;
  24 +import org.thingsboard.server.common.data.edge.Edge;
  25 +import org.thingsboard.server.common.data.page.TextPageLink;
  26 +import org.thingsboard.server.dao.DaoUtil;
  27 +import org.thingsboard.server.dao.edge.EdgeDao;
  28 +import org.thingsboard.server.dao.model.sql.EdgeEntity;
  29 +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
  30 +import org.thingsboard.server.dao.util.SqlDao;
  31 +
  32 +import java.util.List;
  33 +import java.util.Objects;
  34 +import java.util.UUID;
  35 +
  36 +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
  37 +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
  38 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
  39 +
  40 +@Component
  41 +@SqlDao
  42 +public class JpaEdgeDao extends JpaAbstractSearchTextDao<EdgeEntity, Edge> implements EdgeDao {
  43 +
  44 + @Autowired
  45 + private EdgeRepository edgeRepository;
  46 +
  47 + @Override
  48 + public List<Edge> findByTenantIdAndPageLink(UUID tenantId, TextPageLink pageLink) {
  49 + return DaoUtil.convertDataList(edgeRepository
  50 + .findByTenantIdAndPageLink(
  51 + fromTimeUUID(tenantId),
  52 + Objects.toString(pageLink.getTextSearch(), ""),
  53 + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
  54 + new PageRequest(0, pageLink.getLimit())));
  55 + }
  56 +
  57 + @Override
  58 + public ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> edgeIds) {
  59 + return service.submit(() -> DaoUtil.convertDataList(edgeRepository.findEdgesByTenantIdAndIdIn(UUIDConverter.fromTimeUUID(tenantId), fromTimeUUIDs(edgeIds))));
  60 + }
  61 +
  62 + @Override
  63 + protected Class<EdgeEntity> getEntityClass() {
  64 + return EdgeEntity.class;
  65 + }
  66 +
  67 + @Override
  68 + protected CrudRepository<EdgeEntity, String> getCrudRepository() {
  69 + return edgeRepository;
  70 + }
  71 +
  72 +}
... ...
... ... @@ -711,4 +711,21 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_ent
711 711 AND search_text IS NOT NULL
712 712 AND id IS NOT NULL
713 713 PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
714   - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
  714 + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
  715 +
  716 +CREATE TABLE IF NOT EXISTS thingsboard.edge (
  717 + id timeuuid,
  718 + tenant_id timeuuid,
  719 + name text,
  720 + search_text text,
  721 + configuration text,
  722 + additional_info text,
  723 + PRIMARY KEY (id, tenant_id)
  724 +);
  725 +
  726 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.edge_by_tenant_and_search_text AS
  727 + SELECT *
  728 + from thingsboard.edge
  729 + WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
  730 + PRIMARY KEY ( tenant_id, search_text, id )
  731 + WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
\ No newline at end of file
... ...
... ... @@ -243,3 +243,12 @@ CREATE TABLE IF NOT EXISTS entity_view (
243 243 search_text varchar(255),
244 244 additional_info varchar
245 245 );
  246 +
  247 +CREATE TABLE IF NOT EXISTS edge (
  248 + id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
  249 + additional_info varchar,
  250 + configuration varchar(10000000),
  251 + name varchar(255),
  252 + search_text varchar(255),
  253 + tenant_id varchar(31)
  254 +);
\ No newline at end of file
... ...
... ... @@ -20,3 +20,4 @@ DROP TABLE IF EXISTS widgets_bundle;
20 20 DROP TABLE IF EXISTS rule_node;
21 21 DROP TABLE IF EXISTS rule_chain;
22 22 DROP TABLE IF EXISTS entity_view;
  23 +DROP TABLE IF EXISTS edge;
\ No newline at end of file
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +export default angular.module('thingsboard.api.edge', [])
  17 + .factory('edgeService', EdgeService)
  18 + .name;
  19 +
  20 +/*@ngInject*/
  21 +function EdgeService($http, $q) {
  22 +
  23 + var service = {
  24 + getEdges: getEdges,
  25 + getEdgesByIds: getEdgesByIds,
  26 + getEdge: getEdge,
  27 + deleteEdge: deleteEdge,
  28 + saveEdge: saveEdge
  29 + };
  30 +
  31 + return service;
  32 +
  33 + function getEdges(pageLink, config) {
  34 + var deferred = $q.defer();
  35 + var url = '/api/edges?limit=' + pageLink.limit;
  36 + if (angular.isDefined(pageLink.textSearch)) {
  37 + url += '&textSearch=' + pageLink.textSearch;
  38 + }
  39 + if (angular.isDefined(pageLink.idOffset)) {
  40 + url += '&idOffset=' + pageLink.idOffset;
  41 + }
  42 + if (angular.isDefined(pageLink.textOffset)) {
  43 + url += '&textOffset=' + pageLink.textOffset;
  44 + }
  45 + $http.get(url, config).then(function success(response) {
  46 + deferred.resolve(response.data);
  47 + }, function fail() {
  48 + deferred.reject();
  49 + });
  50 + return deferred.promise;
  51 + }
  52 +
  53 + function getEdgesByIds(edgeIds, config) {
  54 + var deferred = $q.defer();
  55 + var ids = '';
  56 + for (var i=0;i<edgeIds.length;i++) {
  57 + if (i>0) {
  58 + ids += ',';
  59 + }
  60 + ids += edgeIds[i];
  61 + }
  62 + var url = '/api/edges?edgeIds=' + ids;
  63 + $http.get(url, config).then(function success(response) {
  64 + var entities = response.data;
  65 + entities.sort(function (entity1, entity2) {
  66 + var id1 = entity1.id.id;
  67 + var id2 = entity2.id.id;
  68 + var index1 = edgeIds.indexOf(id1);
  69 + var index2 = edgeIds.indexOf(id2);
  70 + return index1 - index2;
  71 + });
  72 + deferred.resolve(entities);
  73 + }, function fail() {
  74 + deferred.reject();
  75 + });
  76 + return deferred.promise;
  77 + }
  78 +
  79 + function getEdge(edgeId, config) {
  80 + var deferred = $q.defer();
  81 + var url = '/api/edge/' + edgeId;
  82 + $http.get(url, config).then(function success(response) {
  83 + deferred.resolve(response.data);
  84 + }, function fail(response) {
  85 + deferred.reject(response.data);
  86 + });
  87 + return deferred.promise;
  88 + }
  89 +
  90 + function saveEdge(edge) {
  91 + var deferred = $q.defer();
  92 + var url = '/api/edge';
  93 + $http.post(url, edge).then(function success(response) {
  94 + deferred.resolve(response.data);
  95 + }, function fail(response) {
  96 + deferred.reject(response.data);
  97 + });
  98 + return deferred.promise;
  99 + }
  100 +
  101 + function deleteEdge(edgeId) {
  102 + var deferred = $q.defer();
  103 + var url = '/api/edge/' + edgeId;
  104 + $http.delete(url).then(function success() {
  105 + deferred.resolve();
  106 + }, function fail(response) {
  107 + deferred.reject(response.data);
  108 + });
  109 + return deferred.promise;
  110 + }
  111 +}
... ...
... ... @@ -99,6 +99,7 @@ import thingsboardApiAlarm from './api/alarm.service';
99 99 import thingsboardApiAuditLog from './api/audit-log.service';
100 100 import thingsboardApiComponentDescriptor from './api/component-descriptor.service';
101 101 import thingsboardApiRuleChain from './api/rule-chain.service';
  102 +import thingsboardApiEdge from './api/edge.service';
102 103
103 104 import AppConfig from './app.config';
104 105 import GlobalInterceptor from './global-interceptor.service';
... ... @@ -157,6 +158,7 @@ angular.module('thingsboard', [
157 158 thingsboardApiAuditLog,
158 159 thingsboardApiComponentDescriptor,
159 160 thingsboardApiRuleChain,
  161 + thingsboardApiEdge,
160 162 uiRouter])
161 163 .config(AppConfig)
162 164 .factory('globalInterceptor', GlobalInterceptor)
... ...
... ... @@ -358,7 +358,8 @@ export default angular.module('thingsboard.types', [])
358 358 alarm: "ALARM",
359 359 rulechain: "RULE_CHAIN",
360 360 rulenode: "RULE_NODE",
361   - entityView: "ENTITY_VIEW"
  361 + entityView: "ENTITY_VIEW",
  362 + edge: "EDGE"
362 363 },
363 364 importEntityColumnType: {
364 365 name: {
... ... @@ -461,7 +462,13 @@ export default angular.module('thingsboard.types', [])
461 462 "CURRENT_CUSTOMER": {
462 463 type: 'entity.type-current-customer',
463 464 list: 'entity.type-current-customer'
464   - }
  465 + },
  466 + "EDGE": {
  467 + type: 'entity.type-edge',
  468 + typePlural: 'entity.type-edges',
  469 + list: 'entity.list-of-edges',
  470 + nameStartsWith: 'entity.edge-name-starts-with'
  471 + },
465 472 },
466 473 entitySearchDirection: {
467 474 from: "FROM",
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-dialog aria-label="{{ 'edge.add' | translate }}" tb-help="'edges'" help-container-id="help-container" style="width: 600px;">
  19 + <form name="theForm" ng-submit="vm.add()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2 translate>edge.add</h2>
  23 + <span flex></span>
  24 + <div id="help-container"></div>
  25 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  26 + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
  27 + </md-button>
  28 + </div>
  29 + </md-toolbar>
  30 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
  31 + <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
  32 + <md-dialog-content>
  33 + <div class="md-dialog-content">
  34 + <tb-edge edge="vm.item" is-edit="true" is-create="true" the-form="theForm"></tb-edge>
  35 + </div>
  36 + </md-dialog-content>
  37 + <md-dialog-actions layout="row">
  38 + <span flex></span>
  39 + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
  40 + {{ 'action.add' | translate }}
  41 + </md-button>
  42 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
  43 + </md-dialog-actions>
  44 + </form>
  45 +</md-dialog>
  46 +
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div flex layout="column">
  19 + <div flex style="text-transform: uppercase;">{{ vm.types.edgeType[vm.item.type].name | translate }}</div>
  20 + <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
  21 +</div>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<md-button ng-click="onExportEdge({event: $event})"
  19 + ng-show="!isEdit"
  20 + class="md-raised md-primary">{{ 'edge.export' | translate }}</md-button>
  21 +<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>
  24 +
  25 +<div layout="row">
  26 + <md-button ngclipboard data-clipboard-action="copy"
  27 + ngclipboard-success="onEdgeIdCopied(e)"
  28 + data-clipboard-text="{{edge.id.id}}" ng-show="!isEdit"
  29 + class="md-raised">
  30 + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
  31 + <span translate>edge.copyId</span>
  32 + </md-button>
  33 +</div>
  34 +
  35 +<md-content class="md-padding" layout="column">
  36 + <fieldset ng-disabled="$root.loading || !isEdit">
  37 + <md-input-container class="md-block">
  38 + <label translate>edge.name</label>
  39 + <input required name="name" ng-model="edge.name">
  40 + <div ng-messages="theForm.name.$error">
  41 + <div translate ng-message="required">edge.name-required</div>
  42 + </div>
  43 + </md-input-container>
  44 + <md-input-container class="md-block">
  45 + <label translate>edge.description</label>
  46 + <textarea ng-model="edge.additionalInfo.description" rows="2"></textarea>
  47 + </md-input-container>
  48 + </fieldset>
  49 +</md-content>
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import addEdgeTemplate from './add-edge.tpl.html';
  19 +import edgeCard from './edge-card.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +/*@ngInject*/
  24 +export function EdgeCardController(types) {
  25 +
  26 + var vm = this;
  27 +
  28 + vm.types = types;
  29 +}
  30 +
  31 +
  32 +/*@ngInject*/
  33 +export function EdgeController($rootScope, userService, edgeService, $state, $stateParams,
  34 + $document, $mdDialog, $q, $translate, types, securityTypes, userPermissionsService) {
  35 +
  36 + var edgeActionsList = [];
  37 +
  38 + var edgeGroupActionsList = [];
  39 +
  40 + var vm = this;
  41 +
  42 + vm.types = types;
  43 +
  44 + vm.edgeGridConfig = {
  45 +
  46 + resource: securityTypes.resource.edge,
  47 +
  48 + deleteItemTitleFunc: deleteEdgeTitle,
  49 + deleteItemContentFunc: deleteEdgeText,
  50 + deleteItemsTitleFunc: deleteEdgesTitle,
  51 + deleteItemsActionTitleFunc: deleteEdgesActionTitle,
  52 + deleteItemsContentFunc: deleteEdgesText,
  53 +
  54 + saveItemFunc: saveEdge,
  55 +
  56 + getItemTitleFunc: getEdgeTitle,
  57 +
  58 + itemCardController: 'EdgeCardController',
  59 + itemCardTemplateUrl: edgeCard,
  60 + parentCtl: vm,
  61 +
  62 + actionsList: edgeActionsList,
  63 + groupActionsList: edgeGroupActionsList,
  64 +
  65 + onGridInited: gridInited,
  66 +
  67 + addItemTemplateUrl: addEdgeTemplate,
  68 +
  69 + addItemText: function() { return $translate.instant('edge.add-edge-text') },
  70 + noItemsText: function() { return $translate.instant('edge.no-edges-text') },
  71 + itemDetailsText: function() { return $translate.instant('edge.edge-details') }
  72 + };
  73 +
  74 + if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
  75 + vm.edgeGridConfig.items = $stateParams.items;
  76 + }
  77 +
  78 + if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
  79 + vm.edgeGridConfig.topIndex = $stateParams.topIndex;
  80 + }
  81 +
  82 + initController();
  83 +
  84 + function initController() {
  85 + var fetchEdgesFunction = function (pageLink, edgeType) {
  86 + return edgeService.getEdges(pageLink, true, edgeType);
  87 + };
  88 + var deleteEdgeFunction = function (edgeId) {
  89 + return edgeService.deleteEdge(edgeId);
  90 + };
  91 + var refreshEdgesParamsFunction = function() {
  92 + return {"topIndex": vm.topIndex};
  93 + };
  94 +
  95 + edgeActionsList.push(
  96 + {
  97 + onAction: function ($event, item) {
  98 + vm.grid.deleteItem($event, item);
  99 + },
  100 + name: function() { return $translate.instant('action.delete') },
  101 + details: function() { return $translate.instant('edge.delete') },
  102 + icon: "delete",
  103 + isEnabled: function() {
  104 + return userPermissionsService.hasGenericPermission(securityTypes.resource.edge, securityTypes.operation.delete);
  105 + }
  106 + }
  107 + );
  108 +
  109 + edgeGroupActionsList.push(
  110 + {
  111 + onAction: function ($event) {
  112 + vm.grid.deleteItems($event);
  113 + },
  114 + name: function() { return $translate.instant('edge.delete-edges') },
  115 + details: deleteEdgesActionTitle,
  116 + icon: "delete"
  117 + }
  118 + );
  119 + vm.edgeGridConfig.refreshParamsFunc = refreshEdgesParamsFunction;
  120 + vm.edgeGridConfig.fetchItemsFunc = fetchEdgesFunction;
  121 + vm.edgeGridConfig.deleteItemFunc = deleteEdgeFunction;
  122 +
  123 + }
  124 +
  125 + function deleteEdgeTitle(edge) {
  126 + return $translate.instant('edge.delete-edge-title', {edgeName: edge.name});
  127 + }
  128 +
  129 + function deleteEdgeText() {
  130 + return $translate.instant('edge.delete-edge-text');
  131 + }
  132 +
  133 + function deleteEdgesTitle(selectedCount) {
  134 + return $translate.instant('edge.delete-edges-title', {count: selectedCount}, 'messageformat');
  135 + }
  136 +
  137 + function deleteEdgesActionTitle(selectedCount) {
  138 + return $translate.instant('edge.delete-edges-action-title', {count: selectedCount}, 'messageformat');
  139 + }
  140 +
  141 + function deleteEdgesText () {
  142 + return $translate.instant('edge.delete-edges-text');
  143 + }
  144 +
  145 + function gridInited(grid) {
  146 + vm.grid = grid;
  147 + }
  148 +
  149 + function getEdgeTitle(edge) {
  150 + return edge ? edge.name : '';
  151 + }
  152 +
  153 + function saveEdge(edge) {
  154 + var deferred = $q.defer();
  155 + edgeService.saveEdge(edge).then(
  156 + function success(savedEdge) {
  157 + $rootScope.$broadcast('edgeSaved');
  158 + deferred.resolve(savedEdge);
  159 + },
  160 + function fail() {
  161 + deferred.reject();
  162 + }
  163 + );
  164 + return deferred.promise;
  165 + }
  166 +}
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import edgeFieldsetTemplate from './edge-fieldset.tpl.html';
  19 +
  20 +/* eslint-enable import/no-unresolved, import/default */
  21 +
  22 +/*@ngInject*/
  23 +export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, toast, types) {
  24 + var linker = function (scope, element) {
  25 + var template = $templateCache.get(edgeFieldsetTemplate);
  26 + element.html(template);
  27 +
  28 + scope.types = types;
  29 +
  30 + scope.onEdgeIdCopied = function() {
  31 + toast.showSuccess($translate.instant('edge.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
  32 + };
  33 +
  34 + $compile(element.contents())(scope);
  35 +
  36 + };
  37 + return {
  38 + restrict: "E",
  39 + link: linker,
  40 + scope: {
  41 + edge: '=',
  42 + isEdit: '=',
  43 + theForm: '=',
  44 + isCreate: '<',
  45 + onExportEdge: '&',
  46 + onDeleteEdge: '&'
  47 + }
  48 + };
  49 +}
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import edgesTemplate from './edges.tpl.html';
  19 +
  20 +/* eslint-enable import/no-unresolved, import/default */
  21 +
  22 +/*@ngInject*/
  23 +export default function EdgeRoutes($stateProvider) {
  24 +
  25 + $stateProvider
  26 + .state('home.edges', {
  27 + url: '/edges',
  28 + params: {'topIndex': 0},
  29 + module: 'private',
  30 + auth: ['TENANT_ADMIN'],
  31 + views: {
  32 + "content@home": {
  33 + templateUrl: edgesTemplate,
  34 + controllerAs: 'vm',
  35 + controller: 'EdgeController'
  36 + }
  37 + },
  38 + data: {
  39 + searchEnabled: true,
  40 + pageTitle: 'edge.edges'
  41 + },
  42 + ncyBreadcrumb: {
  43 + label: '{"icon": "transform", "label": "edge.edges"}'
  44 + }
  45 + });
  46 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<tb-grid grid-configuration="vm.edgeGridConfig">
  19 + <details-buttons tb-help="'edges'" help-container-id="help-container">
  20 + <div id="help-container"></div>
  21 + </details-buttons>
  22 + <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
  23 + id="tabs" md-border-bottom flex class="tb-absolute-fill">
  24 + <md-tab label="{{ 'edge.details' | translate }}">
  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>
  29 + </md-tab>
  30 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('edge' | hasGenericPermission:'readAttributes')" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
  31 + <tb-attribute-table flex
  32 + readonly="!('edge' | hasGenericPermission:'writeAttributes')"
  33 + entity-id="vm.grid.operatingItem().id.id"
  34 + entity-type="{{vm.types.entityType.edge}}"
  35 + entity-name="vm.grid.operatingItem().name"
  36 + default-attribute-scope="{{vm.types.attributesScope.server.value}}">
  37 + </tb-attribute-table>
  38 + </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 }}">
  40 + <tb-attribute-table flex
  41 + readonly="!('edge' | hasGenericPermission:'writeTelemetry')"
  42 + entity-id="vm.grid.operatingItem().id.id"
  43 + entity-type="{{vm.types.entityType.edge}}"
  44 + entity-name="vm.grid.operatingItem().name"
  45 + default-attribute-scope="{{vm.types.latestTelemetry.value}}"
  46 + disable-attribute-scope-selection="true">
  47 + </tb-attribute-table>
  48 + </md-tab>
  49 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('alarm' | hasGenericPermission:'read')" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
  50 + <tb-alarm-table flex entity-type="vm.types.entityType.edge"
  51 + entity-id="vm.grid.operatingItem().id.id">
  52 + </tb-alarm-table>
  53 + </md-tab>
  54 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'edge.events' | translate }}">
  55 + <tb-event-table flex entity-type="vm.types.entityType.edge"
  56 + entity-id="vm.grid.operatingItem().id.id"
  57 + tenant-id="vm.grid.operatingItem().tenantId.id"
  58 + default-event-type="{{vm.types.eventType.error.value}}">
  59 + </tb-event-table>
  60 + </md-tab>
  61 + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
  62 + <tb-relation-table flex
  63 + readonly="!('edge' | hasGenericPermission:'write')"
  64 + entity-id="vm.grid.operatingItem().id.id"
  65 + entity-type="{{vm.types.entityType.edge}}">
  66 + </tb-relation-table>
  67 + </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 }}">
  69 + <tb-audit-log-table flex entity-type="vm.types.entityType.edge"
  70 + entity-id="vm.grid.operatingItem().id.id"
  71 + audit-log-mode="{{vm.types.auditLogMode.entity}}">
  72 + </tb-audit-log-table>
  73 + </md-tab>
  74 + </md-tabs>
  75 +</tb-grid>
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import EdgeRoutes from './edge.routes';
  17 +import {EdgeController, EdgeCardController} from './edge.controller';
  18 +import EdgeDirective from './edge.directive';
  19 +
  20 +export default angular.module('thingsboard.edge', [])
  21 + .config(EdgeRoutes)
  22 + .controller('EdgeController', EdgeController)
  23 + .controller('EdgeCardController', EdgeCardController)
  24 + .directive('tbEdge', EdgeDirective)
  25 + .name;
... ...
... ... @@ -179,6 +179,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
179 179 scope.noEntitiesMatchingText = 'customer.no-customers-matching';
180 180 scope.entityRequiredText = 'customer.default-customer-required';
181 181 break;
  182 + case types.entityType.edge:
  183 + scope.selectEntityText = 'edge.select-edge';
  184 + scope.entityText = 'edge.edge';
  185 + scope.noEntitiesMatchingText = 'edge.no-edges-matching';
  186 + scope.entityRequiredText = 'edge.edge-required';
  187 + break;
182 188 }
183 189 if (scope.entity && scope.entity.id.entityType != scope.entityType) {
184 190 scope.entity = null;
... ...
... ... @@ -107,6 +107,7 @@ export default angular.module('thingsboard.help', [])
107 107 widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc",
108 108 widgetsConfigAlarm: helpBaseUrl + "/docs/user-guide/ui/dashboards#alarm",
109 109 widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static",
  110 + edges: helpBaseUrl + "/docs/user-guide/ui/edges"
110 111 },
111 112 getRuleNodeLink: function(ruleNode) {
112 113 if (ruleNode && ruleNode.component) {
... ...
... ... @@ -52,6 +52,7 @@ import thingsboardEntityView from '../entity-view';
52 52 import thingsboardWidgetLibrary from '../widget';
53 53 import thingsboardDashboard from '../dashboard';
54 54 import thingsboardRuleChain from '../rulechain';
  55 +import thingsboardEdge from '../edge';
55 56
56 57 import thingsboardJsonForm from '../jsonform';
57 58
... ... @@ -94,7 +95,8 @@ export default angular.module('thingsboard.home', [
94 95 thingsboardDashboardAutocomplete,
95 96 thingsboardKvMap,
96 97 thingsboardJsonObjectEdit,
97   - thingsboardJsonContent
  98 + thingsboardJsonContent,
  99 + thingsboardEdge
98 100 ])
99 101 .config(HomeRoutes)
100 102 .controller('HomeController', HomeController)
... ...
... ... @@ -705,6 +705,33 @@
705 705 "column": "Column",
706 706 "row": "Row"
707 707 },
  708 + "edge": {
  709 + "edge": "Edge",
  710 + "edges": "Edges",
  711 + "management": "Edge management",
  712 + "no-edges-matching": "No edges matching '{{entity}}' were found.",
  713 + "add": "Add Edge",
  714 + "view": "View Edge",
  715 + "no-edges-text": "No edges found",
  716 + "edge-details": "Edge details",
  717 + "add-edge-text": "Add new edge",
  718 + "delete": "Delete edge",
  719 + "delete-edges": "Delete edges",
  720 + "delete-edge-title": "Are you sure you want to delete the edge '{{edgeName}}'?",
  721 + "delete-edge-text": "Be careful, after the confirmation the edge and all related data will become unrecoverable.",
  722 + "delete-edges-title": "Are you sure you want to edge { count, plural, 1 {1 edge} other {# edges} }?",
  723 + "delete-edges-action-title": "Delete { count, plural, 1 {1 edge} other {# edges} }",
  724 + "delete-edges-text": "Be careful, after the confirmation all selected edges will be removed and all related data will become unrecoverable.",
  725 + "name": "Name",
  726 + "name-required": "Name is required.",
  727 + "description": "Description",
  728 + "events": "Events",
  729 + "details": "Details",
  730 + "copyId": "Copy role Id",
  731 + "idCopiedMessage": "Edge Id has been copied to clipboard",
  732 + "permissions": "Permissions",
  733 + "edge-required": "Edge required",
  734 + },
708 735 "error": {
709 736 "unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
710 737 "unhandled-error-code": "Unhandled error code: {{errorCode}}",
... ... @@ -798,6 +825,10 @@
798 825 "type-rulenodes": "Rule nodes",
799 826 "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }",
800 827 "rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'",
  828 + "type-edge": "Edge",
  829 + "type-edges": "Edges",
  830 + "list-of-edges": "{ count, plural, 1 {One edge} other {List of # edges} }",
  831 + "edge-name-starts-with": "Edges whose names start with '{{prefix}}'",
801 832 "type-current-customer": "Current Customer",
802 833 "search": "Search entities",
803 834 "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",
... ...
... ... @@ -185,6 +185,12 @@ function Menu(userService, $state, $rootScope) {
185 185 icon: 'view_quilt'
186 186 },
187 187 {
  188 + name: 'edge.edges',
  189 + type: 'link',
  190 + state: 'home.edges',
  191 + icon: 'toys'
  192 + },
  193 + {
188 194 name: 'widget.widget-library',
189 195 type: 'link',
190 196 state: 'home.widgets-bundles',
... ... @@ -255,6 +261,16 @@ function Menu(userService, $state, $rootScope) {
255 261 ]
256 262 },
257 263 {
  264 + name: 'edge.management',
  265 + places: [
  266 + {
  267 + name: 'edge.edges',
  268 + icon: 'toys',
  269 + state: 'home.edges'
  270 + }
  271 + ]
  272 + },
  273 + {
258 274 name: 'dashboard.management',
259 275 places: [
260 276 {
... ... @@ -307,6 +323,12 @@ function Menu(userService, $state, $rootScope) {
307 323 icon: 'view_quilt'
308 324 },
309 325 {
  326 + name: 'edge.edges',
  327 + type: 'link',
  328 + state: 'home.edges',
  329 + icon: 'toys'
  330 + },
  331 + {
310 332 name: 'dashboard.dashboards',
311 333 type: 'link',
312 334 state: 'home.dashboards',
... ... @@ -345,6 +367,16 @@ function Menu(userService, $state, $rootScope) {
345 367 ]
346 368 },
347 369 {
  370 + name: 'edge.management',
  371 + places: [
  372 + {
  373 + name: 'edge.edges',
  374 + icon: 'toys',
  375 + state: 'home.edges'
  376 + }
  377 + ]
  378 + },
  379 + {
348 380 name: 'dashboard.view-dashboards',
349 381 places: [
350 382 {
... ...