Browse Source

Added Edge entity

pull/2818/head
Volodymyr Babak 7 years ago
parent
commit
3880af82d3
  1. 32
      application/src/main/data/upgrade/2.4.x/schema_update.cql
  2. 24
      application/src/main/data/upgrade/2.4.x/schema_update.sql
  3. 19
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 163
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  5. 5
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  6. 8
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  7. 8
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  9. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  10. 206
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  11. 23
      application/src/test/java/org/thingsboard/server/controller/nosql/EdgeControllerNoSqlTest.java
  12. 23
      application/src/test/java/org/thingsboard/server/controller/sql/EdgeControllerSqlTest.java
  13. 45
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  14. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  15. 65
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  16. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  18. 156
      dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeService.java
  19. 77
      dao/src/main/java/org/thingsboard/server/dao/edge/CassandraEdgeDao.java
  20. 51
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java
  21. 12
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  22. 98
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/EdgeEntity.java
  23. 108
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java
  24. 40
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java
  25. 72
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java
  26. 19
      dao/src/main/resources/cassandra/schema-entities.cql
  27. 9
      dao/src/main/resources/sql/schema-entities.sql
  28. 1
      dao/src/test/resources/sql/drop-all-tables.sql
  29. 111
      ui/src/app/api/edge.service.js
  30. 2
      ui/src/app/app.js
  31. 11
      ui/src/app/common/types.constant.js
  32. 46
      ui/src/app/edge/add-edge.tpl.html
  33. 21
      ui/src/app/edge/edge-card.tpl.html
  34. 49
      ui/src/app/edge/edge-fieldset.tpl.html
  35. 166
      ui/src/app/edge/edge.controller.js
  36. 49
      ui/src/app/edge/edge.directive.js
  37. 46
      ui/src/app/edge/edge.routes.js
  38. 75
      ui/src/app/edge/edges.tpl.html
  39. 25
      ui/src/app/edge/index.js
  40. 6
      ui/src/app/entity/entity-autocomplete.directive.js
  41. 1
      ui/src/app/help/help-links.constant.js
  42. 4
      ui/src/app/layout/index.js
  43. 31
      ui/src/app/locale/locale.constant-en_US.json
  44. 32
      ui/src/app/services/menu.service.js

32
application/src/main/data/upgrade/2.4.x/schema_update.cql

@ -0,0 +1,32 @@
--
-- Copyright © 2016-2019 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS thingsboard.edge (
id timeuuid,
tenant_id timeuuid,
name text,
search_text text,
configuration text,
additional_info text,
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.edge_by_tenant_and_search_text AS
SELECT *
from thingsboard.edge
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );

24
application/src/main/data/upgrade/2.4.x/schema_update.sql

@ -0,0 +1,24 @@
--
-- Copyright © 2016-2019 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS edge (
id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
additional_info varchar,
configuration varchar(10000000),
name varchar(255),
search_text varchar(255),
tenant_id varchar(31)
);

19
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -43,12 +43,14 @@ import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
@ -82,6 +84,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -185,6 +188,9 @@ public abstract class BaseController {
@Autowired
protected ClaimDevicesService claimDevicesService;
@Autowired
protected EdgeService edgeService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ -458,6 +464,18 @@ public abstract class BaseController {
}
}
Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException {
try {
validateId(edgeId, "Incorrect edgeId " + edgeId);
Edge edge = edgeService.findEdgeById(getTenantId(), edgeId);
checkNotNull(edge);
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
return edge;
} catch (Exception e) {
throw handleException(e, false);
}
}
DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
@ -513,7 +531,6 @@ public abstract class BaseController {
return ruleNode;
}
protected String constructBaseUrl(HttpServletRequest request) {
String scheme = request.getScheme();
if (request.getHeader("x-forwarded-proto") != null) {

163
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -0,0 +1,163 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class EdgeController extends BaseController {
private static final String EDGE_ID = "edgeId";
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
@ResponseBody
public Edge getEdgeById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
return checkEdgeId(edgeId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge", method = RequestMethod.POST)
@ResponseBody
public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException {
try {
edge.setTenantId(getCurrentUser().getTenantId());
boolean created = edge.getId() == null;
Operation operation = created ? Operation.CREATE : Operation.WRITE;
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge);
Edge result = checkNotNull(edgeService.saveEdge(edge));
logEntityAction(result.getId(), result, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
return result;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.EDGE), edge,
null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edges", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TextPageData<Edge> getEdges(
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ);
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return checkNotNull(edgeService.findTenantEdges(tenantId, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteEdge(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.DELETE);
edgeService.deleteEdge(getTenantId(), edgeId);
logEntityAction(edgeId, edge,
null,
ActionType.DELETED, null, strEdgeId);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.EDGE),
null,
null,
ActionType.DELETED, e, strEdgeId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
@ResponseBody
public List<Edge> getEdgesByIds(
@RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException {
checkArrayParameter("edgeIds", strEdgeIds);
try {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ);
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
List<EdgeId> edgeIds = new ArrayList<>();
for (String strEdgeId : strEdgeIds) {
edgeIds.add(new EdgeId(toUUID(strEdgeId)));
}
List<Edge> edges = checkNotNull(edgeService.findEdgesByIdsAsync(tenantId, edgeIds).get());
return filterEdgesByReadPermission(edges);
} catch (Exception e) {
throw handleException(e);
}
}
private List<Edge> filterEdgesByReadPermission(List<Edge> edges) {
return edges.stream().filter(edge -> {
try {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ, edge.getId(), edge);
return true;
} catch (ThingsboardException e) {
return false;
}
}).collect(Collectors.toList());
}
}

5
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -119,6 +119,11 @@ public class ThingsboardInstallService {
case "2.4.0":
log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ...");
case "2.4.2":
log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.x ...");
databaseUpgradeService.upgradeDatabase("2.4.2");
log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts");

8
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -266,6 +266,14 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
Thread.sleep(2500);
} catch (InvalidQueryException e) {}
log.info("Schema updated.");
break;
case "2.4.2":
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.x", SCHEMA_UPDATE_CQL);
loadCql(schemaUpdateFile);
log.info("Schema updated.");
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);

8
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -176,6 +176,14 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated.");
}
break;
case "2.4.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.x", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Schema updated.");
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

3
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -31,7 +31,8 @@ public enum Resource {
RULE_CHAIN(EntityType.RULE_CHAIN),
USER(EntityType.USER),
WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE),
WIDGET_TYPE(EntityType.WIDGET_TYPE);
WIDGET_TYPE(EntityType.WIDGET_TYPE),
EDGE(EntityType.EDGE);
private final EntityType entityType;

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.USER, userPermissionChecker);
put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
put(Resource.EDGE, tenantEntityPermissionChecker);
}
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {

206
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -0,0 +1,206 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
private IdComparator<Edge> idComparator;
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
idComparator = new IdComparator<>();
savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindEdgeById() throws Exception {
Edge savedEdge = getNewSavedEdge("Test edge");
Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
Assert.assertNotNull(foundEdge);
assertEquals(savedEdge, foundEdge);
}
@Test
public void testSaveEdge() throws Exception {
Edge savedEdge = getNewSavedEdge("Test edge");
Assert.assertNotNull(savedEdge);
Assert.assertNotNull(savedEdge.getId());
Assert.assertTrue(savedEdge.getCreatedTime() > 0);
assertEquals(savedTenant.getId(), savedEdge.getTenantId());
savedEdge.setName("New test edge");
doPost("/api/edge", savedEdge, Edge.class);
Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
assertEquals(foundEdge.getName(), savedEdge.getName());
}
@Test
public void testDeleteEdge() throws Exception {
Edge edge = getNewSavedEdge("Test edge");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
doDelete("/api/edge/" + savedEdge.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/edge/" + savedEdge.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test
public void testSaveEdgeWithEmptyName() throws Exception {
Edge edge = new Edge();
doPost("/api/edge", edge)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Edge name should be specified!")));
}
@Test
public void testGetEdges() throws Exception {
List<Edge> edges = new ArrayList<>();
for (int i = 0; i < 178; i++) {
edges.add(getNewSavedEdge("Test edge " + i));
}
List<Edge> loadedEdges = loadListOf(new TextPageLink(23), "/api/edges?");
Collections.sort(edges, idComparator);
Collections.sort(loadedEdges, idComparator);
assertEquals(edges, loadedEdges);
}
@Test
public void testGetEdgesByName() throws Exception {
String name1 = "Entity edge1";
List<Edge> namesOfEdge1 = fillListOf(143, name1);
List<Edge> loadedNamesOfEdge1 = loadListOf(new TextPageLink(15, name1), "/api/edges?");
Collections.sort(namesOfEdge1, idComparator);
Collections.sort(loadedNamesOfEdge1, idComparator);
assertEquals(namesOfEdge1, loadedNamesOfEdge1);
String name2 = "Entity edge2";
List<Edge> namesOfEdge2 = fillListOf(75, name2);
List<Edge> loadedNamesOfEdge2 = loadListOf(new TextPageLink(4, name2), "/api/edges?");
Collections.sort(namesOfEdge2, idComparator);
Collections.sort(loadedNamesOfEdge2, idComparator);
assertEquals(namesOfEdge2, loadedNamesOfEdge2);
for (Edge edge : loadedNamesOfEdge1) {
doDelete("/api/edge/" + edge.getId().getId().toString()).andExpect(status().isOk());
}
TextPageData<Edge> pageData = doGetTypedWithPageLink("/api/edges?",
new TypeReference<TextPageData<Edge>>() {
}, new TextPageLink(4, name1));
Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size());
for (Edge edge : loadedNamesOfEdge2) {
doDelete("/api/edge/" + edge.getId().getId().toString()).andExpect(status().isOk());
}
pageData = doGetTypedWithPageLink("/api/edges?", new TypeReference<TextPageData<Edge>>() {
}, new TextPageLink(4, name2));
Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size());
}
private Edge getNewSavedEdge(String name) throws Exception {
Edge edge = createEdge(name);
return doPost("/api/edge", edge, Edge.class);
}
private Edge createEdge(String name) {
Edge edge = new Edge();
edge.setTenantId(savedTenant.getId());
edge.setName(name);
return edge;
}
private Tenant getNewTenant(String title) {
Tenant tenant = new Tenant();
tenant.setTitle(title);
return tenant;
}
private List<Edge> fillListOf(int limit, String partOfName) throws Exception {
List<Edge> edgeNames = new ArrayList<>();
for (int i = 0; i < limit; i++) {
String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15);
fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase();
Edge edge = getNewSavedEdge(fullName);
edgeNames.add(doPost("/api/edge", edge, Edge.class));
}
return edgeNames;
}
private List<Edge> loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception {
List<Edge> loadedItems = new ArrayList<>();
TextPageData<Edge> pageData;
do {
pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<TextPageData<Edge>>() {
}, pageLink);
loadedItems.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return loadedItems;
}
}

23
application/src/test/java/org/thingsboard/server/controller/nosql/EdgeControllerNoSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.nosql;
import org.thingsboard.server.controller.BaseEdgeControllerTest;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
@DaoNoSqlTest
public class EdgeControllerNoSqlTest extends BaseEdgeControllerTest {
}

23
application/src/test/java/org/thingsboard/server/controller/sql/EdgeControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseEdgeControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class EdgeControllerSqlTest extends BaseEdgeControllerTest {
}

45
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.edge;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import java.util.List;
public interface EdgeService {
Edge saveEdge(Edge edge);
Edge findEdgeById(TenantId tenantId, EdgeId edgeId);
ListenableFuture<Edge> findEdgeByIdAsync(TenantId tenantId, EdgeId edgeId);
ListenableFuture<List<Edge>> findEdgesByIdsAsync(TenantId tenantId, List<EdgeId> edgeIds);
List<Edge> findAllEdges(TenantId tenantId);
TextPageData<Edge> findTenantEdges(TenantId tenantId, TextPageLink pageLink);
void deleteEdge(TenantId tenantId, EdgeId edgeId);
void deleteEdgesByTenantId(TenantId tenantId);
}

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE
TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, EDGE
}

65
common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java

@ -0,0 +1,65 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.edge;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
@EqualsAndHashCode(callSuper = true)
@ToString
@Getter
@Setter
public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasName, HasTenantId, HasCustomerId {
private static final long serialVersionUID = 4934987555236873728L;
private TenantId tenantId;
private CustomerId customerId;
private String name;
private transient JsonNode configuration;
private transient JsonNode additionalInfo;
public Edge() {
super();
}
public Edge(EdgeId id) {
super(id);
}
public Edge(Edge edge) {
super(edge);
this.tenantId = edge.getTenantId();
this.name = edge.getName();
this.configuration = edge.getConfiguration();
this.additionalInfo = edge.getAdditionalInfo();
}
@Override
public String getSearchText() {
return getName();
}
}

43
common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class EdgeId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public EdgeId(@JsonProperty("id") UUID id) {
super(id);
}
public static EdgeId fromString(String integrationId) {
return new EdgeId(UUID.fromString(integrationId));
}
@JsonIgnore
@Override
public EntityType getEntityType() {
return EntityType.EDGE;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -63,6 +63,8 @@ public class EntityIdFactory {
return new WidgetsBundleId(uuid);
case WIDGET_TYPE:
return new WidgetTypeId(uuid);
case EDGE:
return new EdgeId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}

156
dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeService.java

@ -0,0 +1,156 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.edge;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.List;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@Slf4j
public class BaseEdgeService extends AbstractEntityService implements EdgeService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
public static final String INCORRECT_EDGE_ID = "Incorrect edgeId ";
@Autowired
private EdgeDao edgeDao;
@Autowired
private TenantDao tenantDao;
@Override
public Edge saveEdge(Edge edge) {
log.trace("Executing saveEdge [{}]", edge);
edgeValidator.validate(edge, Edge::getTenantId);
return edgeDao.save(edge.getTenantId(), edge);
}
@Override
public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing findEdgeById [{}]", edgeId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
return edgeDao.findById(tenantId, edgeId.getId());
}
@Override
public ListenableFuture<Edge> findEdgeByIdAsync(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing findEdgeByIdAsync [{}]", edgeId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
return edgeDao.findByIdAsync(tenantId, edgeId.getId());
}
@Override
public ListenableFuture<List<Edge>> findEdgesByIdsAsync(TenantId tenantId, List<EdgeId> edgeIds) {
log.trace("Executing findEdgesByIdsAsync, tenantId [{}], edgeIds [{}]", tenantId, edgeIds);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateIds(edgeIds, "Incorrect edgeIds " + edgeIds);
return edgeDao.findEdgesByTenantIdAndIdsAsync(tenantId.getId(), toUUIDs(edgeIds));
}
@Override
public List<Edge> findAllEdges(TenantId tenantId) {
log.trace("Executing findAllEdges");
return edgeDao.find(tenantId);
}
@Override
public TextPageData<Edge> findTenantEdges(TenantId tenantId, TextPageLink pageLink) {
log.trace("Executing findTenantEdges, tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
List<Edge> edges = edgeDao.findByTenantIdAndPageLink(tenantId.getId(), pageLink);
return new TextPageData<>(edges, pageLink);
}
@Override
public void deleteEdge(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing deleteEdge [{}]", edgeId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
deleteEntityRelations(tenantId, edgeId);
edgeDao.removeById(tenantId, edgeId.getId());
}
@Override
public void deleteEdgesByTenantId(TenantId tenantId) {
log.trace("Executing deleteEdgesByTenantId, tenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
tenantEdgesRemover.removeEntities(tenantId, tenantId);
}
private DataValidator<Edge> edgeValidator =
new DataValidator<Edge>() {
@Override
protected void validateCreate(TenantId tenantId, Edge edge) {
}
@Override
protected void validateUpdate(TenantId tenantId, Edge edge) {
}
@Override
protected void validateDataImpl(TenantId tenantId, Edge edge) {
if (StringUtils.isEmpty(edge.getName())) {
throw new DataValidationException("Edge name should be specified!");
}
if (edge.getTenantId() == null || edge.getTenantId().isNullUid()) {
throw new DataValidationException("Edge should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, edge.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Edge is referencing to non-existent tenant!");
}
}
}
};
private PaginatedRemover<TenantId, Edge> tenantEdgesRemover =
new PaginatedRemover<TenantId, Edge>() {
@Override
protected List<Edge> findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) {
return edgeDao.findByTenantIdAndPageLink(id.getId(), pageLink);
}
@Override
protected void removeEntity(TenantId tenantId, Edge entity) {
deleteEdge(tenantId, new EdgeId(entity.getId().getId()));
}
};
}

77
dao/src/main/java/org/thingsboard/server/dao/edge/CassandraEdgeDao.java

@ -0,0 +1,77 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.edge;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.nosql.EdgeEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.util.NoSqlDao;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static com.datastax.driver.core.querybuilder.QueryBuilder.in;
import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
@Component
@Slf4j
@NoSqlDao
public class CassandraEdgeDao extends CassandraAbstractSearchTextDao<EdgeEntity, Edge> implements EdgeDao {
@Override
protected Class<EdgeEntity> getColumnFamilyClass() {
return EdgeEntity.class;
}
@Override
protected String getColumnFamilyName() {
return EDGE_COLUMN_FAMILY_NAME;
}
@Override
public List<Edge> findByTenantIdAndPageLink(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find edges by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<EdgeEntity> edgeEntities = findPageWithTextSearch(new TenantId(tenantId), EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Collections.singletonList(eq(EDGE_TENANT_ID_PROPERTY, tenantId)), pageLink);
log.trace("Found edges [{}] by tenantId [{}] and pageLink [{}]", edgeEntities, tenantId, pageLink);
return DaoUtil.convertDataList(edgeEntities);
}
@Override
public ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> edgeIds) {
log.debug("Try to find edges by tenantId [{}] and edge Ids [{}]", tenantId, edgeIds);
Select select = select().from(getColumnFamilyName());
Select.Where query = select.where();
query.and(eq(EDGE_TENANT_ID_PROPERTY, tenantId));
query.and(in(ID_PROPERTY, edgeIds));
return findListByStatementAsync(new TenantId(tenantId), query);
}
}

51
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.edge;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
import java.util.UUID;
/**
* The Interface EdgeDao.
*
*/
public interface EdgeDao extends Dao<Edge> {
/**
* Find edges by tenantId and page link.
*
* @param tenantId the tenantId
* @param pageLink the page link
* @return the list of edge objects
*/
List<Edge> findByTenantIdAndPageLink(UUID tenantId, TextPageLink pageLink);
/**
* Find edges by tenantId and edge Ids.
*
* @param tenantId the tenantId
* @param edgeIds the edge Ids
* @return the list of edge objects
*/
ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> edgeIds);
}

12
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -347,6 +347,18 @@ public class ModelConstants {
public static final String RULE_NODE_NAME_PROPERTY = "name";
public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration";
/**
* Cassandra edge constants.
*/
public static final String EDGE_COLUMN_FAMILY_NAME = "edge";
public static final String EDGE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String EDGE_NAME_PROPERTY = "name";
public static final String EDGE_CONFIGURATION_PROPERTY = "configuration";
public static final String EDGE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_tenant_and_search_text";
/**
* Cassandra attributes and timeseries constants.
*/

98
dao/src/main/java/org/thingsboard/server/dao/model/nosql/EdgeEntity.java

@ -0,0 +1,98 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.nosql;
import com.datastax.driver.core.utils.UUIDs;
import com.datastax.driver.mapping.annotations.ClusteringColumn;
import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.model.type.JsonCodec;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ADDITIONAL_INFO_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CONFIGURATION_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@Data
@Table(name = EDGE_COLUMN_FAMILY_NAME)
public class EdgeEntity implements SearchTextEntity<Edge> {
@PartitionKey
@Column(name = ID_PROPERTY)
private UUID id;
@ClusteringColumn
@Column(name = EDGE_TENANT_ID_PROPERTY)
private UUID tenantId;
@Column(name = EDGE_NAME_PROPERTY)
private String name;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Column(name = EDGE_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
private JsonNode configuration;
@Column(name = EDGE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
private JsonNode additionalInfo;
public EdgeEntity() {
super();
}
public EdgeEntity(Edge edge) {
if (edge.getId() != null) {
this.id = edge.getId().getId();
}
if (edge.getTenantId() != null) {
this.tenantId = edge.getTenantId().getId();
}
this.name = edge.getName();
this.configuration = edge.getConfiguration();
this.additionalInfo = edge.getAdditionalInfo();
}
@Override
public String getSearchTextSource() {
return getName();
}
@Override
public Edge toData() {
Edge edge = new Edge(new EdgeId(id));
edge.setCreatedTime(UUIDs.unixTimestamp(id));
if (tenantId != null) {
edge.setTenantId(new TenantId(tenantId));
}
edge.setName(name);
edge.setConfiguration(configuration);
edge.setAdditionalInfo(additionalInfo);
return edge;
}
}

108
dao/src/main/java/org/thingsboard/server/dao/model/sql/EdgeEntity.java

@ -0,0 +1,108 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = EDGE_COLUMN_FAMILY_NAME)
public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity<Edge> {
@Column(name = EDGE_TENANT_ID_PROPERTY)
private String tenantId;
@Column(name = EDGE_NAME_PROPERTY)
private String name;
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
@Type(type = "json")
@Column(name = ModelConstants.EDGE_CONFIGURATION_PROPERTY)
private JsonNode configuration;
@Type(type = "json")
@Column(name = ModelConstants.EDGE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
public EdgeEntity() {
super();
}
public EdgeEntity(Edge edge) {
if (edge.getId() != null) {
this.setId(edge.getId().getId());
}
if (edge.getTenantId() != null) {
this.tenantId = UUIDConverter.fromTimeUUID(edge.getTenantId().getId());
}
this.name = edge.getName();
this.configuration = edge.getConfiguration();
this.additionalInfo = edge.getAdditionalInfo();
}
public String getSearchText() {
return searchText;
}
@Override
public String getSearchTextSource() {
return name;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
@Override
public Edge toData() {
Edge edge = new Edge(new EdgeId(UUIDConverter.fromString(id)));
edge.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id)));
if (tenantId != null) {
edge.setTenantId(new TenantId(UUIDConverter.fromString(tenantId)));
}
edge.setName(name);
edge.setConfiguration(configuration);
edge.setAdditionalInfo(additionalInfo);
return edge;
}
}

40
dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.edge;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.EdgeEntity;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
@SqlDao
public interface EdgeRepository extends CrudRepository<EdgeEntity, String> {
@Query("SELECT a FROM EdgeEntity a WHERE a.tenantId = :tenantId " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " +
"AND a.id > :idOffset ORDER BY a.id")
List<EdgeEntity> findByTenantIdAndPageLink(@Param("tenantId") String tenantId,
@Param("textSearch") String textSearch,
@Param("idOffset") String idOffset,
Pageable pageable);
List<EdgeEntity> findEdgesByTenantIdAndIdIn(String tenantId, List<String> edgeIds);
}

72
dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java

@ -0,0 +1,72 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.edge;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.model.sql.EdgeEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
@Component
@SqlDao
public class JpaEdgeDao extends JpaAbstractSearchTextDao<EdgeEntity, Edge> implements EdgeDao {
@Autowired
private EdgeRepository edgeRepository;
@Override
public List<Edge> findByTenantIdAndPageLink(UUID tenantId, TextPageLink pageLink) {
return DaoUtil.convertDataList(edgeRepository
.findByTenantIdAndPageLink(
fromTimeUUID(tenantId),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()),
new PageRequest(0, pageLink.getLimit())));
}
@Override
public ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> edgeIds) {
return service.submit(() -> DaoUtil.convertDataList(edgeRepository.findEdgesByTenantIdAndIdIn(UUIDConverter.fromTimeUUID(tenantId), fromTimeUUIDs(edgeIds))));
}
@Override
protected Class<EdgeEntity> getEntityClass() {
return EdgeEntity.class;
}
@Override
protected CrudRepository<EdgeEntity, String> getCrudRepository() {
return edgeRepository;
}
}

19
dao/src/main/resources/cassandra/schema-entities.cql

@ -711,4 +711,21 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_ent
AND search_text IS NOT NULL
AND id IS NOT NULL
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
CREATE TABLE IF NOT EXISTS thingsboard.edge (
id timeuuid,
tenant_id timeuuid,
name text,
search_text text,
configuration text,
additional_info text,
PRIMARY KEY (id, tenant_id)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.edge_by_tenant_and_search_text AS
SELECT *
from thingsboard.edge
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, search_text, id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );

9
dao/src/main/resources/sql/schema-entities.sql

@ -243,3 +243,12 @@ CREATE TABLE IF NOT EXISTS entity_view (
search_text varchar(255),
additional_info varchar
);
CREATE TABLE IF NOT EXISTS edge (
id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
additional_info varchar,
configuration varchar(10000000),
name varchar(255),
search_text varchar(255),
tenant_id varchar(31)
);

1
dao/src/test/resources/sql/drop-all-tables.sql

@ -20,3 +20,4 @@ DROP TABLE IF EXISTS widgets_bundle;
DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS entity_view;
DROP TABLE IF EXISTS edge;

111
ui/src/app/api/edge.service.js

@ -0,0 +1,111 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default angular.module('thingsboard.api.edge', [])
.factory('edgeService', EdgeService)
.name;
/*@ngInject*/
function EdgeService($http, $q) {
var service = {
getEdges: getEdges,
getEdgesByIds: getEdgesByIds,
getEdge: getEdge,
deleteEdge: deleteEdge,
saveEdge: saveEdge
};
return service;
function getEdges(pageLink, config) {
var deferred = $q.defer();
var url = '/api/edges?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
url += '&textSearch=' + pageLink.textSearch;
}
if (angular.isDefined(pageLink.idOffset)) {
url += '&idOffset=' + pageLink.idOffset;
}
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getEdgesByIds(edgeIds, config) {
var deferred = $q.defer();
var ids = '';
for (var i=0;i<edgeIds.length;i++) {
if (i>0) {
ids += ',';
}
ids += edgeIds[i];
}
var url = '/api/edges?edgeIds=' + ids;
$http.get(url, config).then(function success(response) {
var entities = response.data;
entities.sort(function (entity1, entity2) {
var id1 = entity1.id.id;
var id2 = entity2.id.id;
var index1 = edgeIds.indexOf(id1);
var index2 = edgeIds.indexOf(id2);
return index1 - index2;
});
deferred.resolve(entities);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function getEdge(edgeId, config) {
var deferred = $q.defer();
var url = '/api/edge/' + edgeId;
$http.get(url, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function saveEdge(edge) {
var deferred = $q.defer();
var url = '/api/edge';
$http.post(url, edge).then(function success(response) {
deferred.resolve(response.data);
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
function deleteEdge(edgeId) {
var deferred = $q.defer();
var url = '/api/edge/' + edgeId;
$http.delete(url).then(function success() {
deferred.resolve();
}, function fail(response) {
deferred.reject(response.data);
});
return deferred.promise;
}
}

2
ui/src/app/app.js

@ -99,6 +99,7 @@ import thingsboardApiAlarm from './api/alarm.service';
import thingsboardApiAuditLog from './api/audit-log.service';
import thingsboardApiComponentDescriptor from './api/component-descriptor.service';
import thingsboardApiRuleChain from './api/rule-chain.service';
import thingsboardApiEdge from './api/edge.service';
import AppConfig from './app.config';
import GlobalInterceptor from './global-interceptor.service';
@ -157,6 +158,7 @@ angular.module('thingsboard', [
thingsboardApiAuditLog,
thingsboardApiComponentDescriptor,
thingsboardApiRuleChain,
thingsboardApiEdge,
uiRouter])
.config(AppConfig)
.factory('globalInterceptor', GlobalInterceptor)

11
ui/src/app/common/types.constant.js

@ -358,7 +358,8 @@ export default angular.module('thingsboard.types', [])
alarm: "ALARM",
rulechain: "RULE_CHAIN",
rulenode: "RULE_NODE",
entityView: "ENTITY_VIEW"
entityView: "ENTITY_VIEW",
edge: "EDGE"
},
importEntityColumnType: {
name: {
@ -461,7 +462,13 @@ export default angular.module('thingsboard.types', [])
"CURRENT_CUSTOMER": {
type: 'entity.type-current-customer',
list: 'entity.type-current-customer'
}
},
"EDGE": {
type: 'entity.type-edge',
typePlural: 'entity.type-edges',
list: 'entity.list-of-edges',
nameStartsWith: 'entity.edge-name-starts-with'
},
},
entitySearchDirection: {
from: "FROM",

46
ui/src/app/edge/add-edge.tpl.html

@ -0,0 +1,46 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-dialog aria-label="{{ 'edge.add' | translate }}" tb-help="'edges'" help-container-id="help-container" style="width: 600px;">
<form name="theForm" ng-submit="vm.add()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>edge.add</h2>
<span flex></span>
<div id="help-container"></div>
<md-button class="md-icon-button" ng-click="vm.cancel()">
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
</md-button>
</div>
</md-toolbar>
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
<tb-edge edge="vm.item" is-edit="true" is-create="true" the-form="theForm"></tb-edge>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
{{ 'action.add' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
</form>
</md-dialog>

21
ui/src/app/edge/edge-card.tpl.html

@ -0,0 +1,21 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div flex layout="column">
<div flex style="text-transform: uppercase;">{{ vm.types.edgeType[vm.item.type].name | translate }}</div>
<div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
</div>

49
ui/src/app/edge/edge-fieldset.tpl.html

@ -0,0 +1,49 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<md-button ng-click="onExportEdge({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'edge.export' | translate }}</md-button>
<md-button ng-click="onDeleteEdge({event: $event})"
ng-if="'edge' | hasGenericPermission:'delete'"
ng-show="!isEdit" class="md-raised md-primary">{{ 'edge.delete' | translate }}</md-button>
<div layout="row">
<md-button ngclipboard data-clipboard-action="copy"
ngclipboard-success="onEdgeIdCopied(e)"
data-clipboard-text="{{edge.id.id}}" ng-show="!isEdit"
class="md-raised">
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>edge.copyId</span>
</md-button>
</div>
<md-content class="md-padding" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit">
<md-input-container class="md-block">
<label translate>edge.name</label>
<input required name="name" ng-model="edge.name">
<div ng-messages="theForm.name.$error">
<div translate ng-message="required">edge.name-required</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label translate>edge.description</label>
<textarea ng-model="edge.additionalInfo.description" rows="2"></textarea>
</md-input-container>
</fieldset>
</md-content>

166
ui/src/app/edge/edge.controller.js

@ -0,0 +1,166 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import addEdgeTemplate from './add-edge.tpl.html';
import edgeCard from './edge-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export function EdgeCardController(types) {
var vm = this;
vm.types = types;
}
/*@ngInject*/
export function EdgeController($rootScope, userService, edgeService, $state, $stateParams,
$document, $mdDialog, $q, $translate, types, securityTypes, userPermissionsService) {
var edgeActionsList = [];
var edgeGroupActionsList = [];
var vm = this;
vm.types = types;
vm.edgeGridConfig = {
resource: securityTypes.resource.edge,
deleteItemTitleFunc: deleteEdgeTitle,
deleteItemContentFunc: deleteEdgeText,
deleteItemsTitleFunc: deleteEdgesTitle,
deleteItemsActionTitleFunc: deleteEdgesActionTitle,
deleteItemsContentFunc: deleteEdgesText,
saveItemFunc: saveEdge,
getItemTitleFunc: getEdgeTitle,
itemCardController: 'EdgeCardController',
itemCardTemplateUrl: edgeCard,
parentCtl: vm,
actionsList: edgeActionsList,
groupActionsList: edgeGroupActionsList,
onGridInited: gridInited,
addItemTemplateUrl: addEdgeTemplate,
addItemText: function() { return $translate.instant('edge.add-edge-text') },
noItemsText: function() { return $translate.instant('edge.no-edges-text') },
itemDetailsText: function() { return $translate.instant('edge.edge-details') }
};
if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
vm.edgeGridConfig.items = $stateParams.items;
}
if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
vm.edgeGridConfig.topIndex = $stateParams.topIndex;
}
initController();
function initController() {
var fetchEdgesFunction = function (pageLink, edgeType) {
return edgeService.getEdges(pageLink, true, edgeType);
};
var deleteEdgeFunction = function (edgeId) {
return edgeService.deleteEdge(edgeId);
};
var refreshEdgesParamsFunction = function() {
return {"topIndex": vm.topIndex};
};
edgeActionsList.push(
{
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
},
name: function() { return $translate.instant('action.delete') },
details: function() { return $translate.instant('edge.delete') },
icon: "delete",
isEnabled: function() {
return userPermissionsService.hasGenericPermission(securityTypes.resource.edge, securityTypes.operation.delete);
}
}
);
edgeGroupActionsList.push(
{
onAction: function ($event) {
vm.grid.deleteItems($event);
},
name: function() { return $translate.instant('edge.delete-edges') },
details: deleteEdgesActionTitle,
icon: "delete"
}
);
vm.edgeGridConfig.refreshParamsFunc = refreshEdgesParamsFunction;
vm.edgeGridConfig.fetchItemsFunc = fetchEdgesFunction;
vm.edgeGridConfig.deleteItemFunc = deleteEdgeFunction;
}
function deleteEdgeTitle(edge) {
return $translate.instant('edge.delete-edge-title', {edgeName: edge.name});
}
function deleteEdgeText() {
return $translate.instant('edge.delete-edge-text');
}
function deleteEdgesTitle(selectedCount) {
return $translate.instant('edge.delete-edges-title', {count: selectedCount}, 'messageformat');
}
function deleteEdgesActionTitle(selectedCount) {
return $translate.instant('edge.delete-edges-action-title', {count: selectedCount}, 'messageformat');
}
function deleteEdgesText () {
return $translate.instant('edge.delete-edges-text');
}
function gridInited(grid) {
vm.grid = grid;
}
function getEdgeTitle(edge) {
return edge ? edge.name : '';
}
function saveEdge(edge) {
var deferred = $q.defer();
edgeService.saveEdge(edge).then(
function success(savedEdge) {
$rootScope.$broadcast('edgeSaved');
deferred.resolve(savedEdge);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
}

49
ui/src/app/edge/edge.directive.js

@ -0,0 +1,49 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import edgeFieldsetTemplate from './edge-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, toast, types) {
var linker = function (scope, element) {
var template = $templateCache.get(edgeFieldsetTemplate);
element.html(template);
scope.types = types;
scope.onEdgeIdCopied = function() {
toast.showSuccess($translate.instant('edge.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
$compile(element.contents())(scope);
};
return {
restrict: "E",
link: linker,
scope: {
edge: '=',
isEdit: '=',
theForm: '=',
isCreate: '<',
onExportEdge: '&',
onDeleteEdge: '&'
}
};
}

46
ui/src/app/edge/edge.routes.js

@ -0,0 +1,46 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable import/no-unresolved, import/default */
import edgesTemplate from './edges.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EdgeRoutes($stateProvider) {
$stateProvider
.state('home.edges', {
url: '/edges',
params: {'topIndex': 0},
module: 'private',
auth: ['TENANT_ADMIN'],
views: {
"content@home": {
templateUrl: edgesTemplate,
controllerAs: 'vm',
controller: 'EdgeController'
}
},
data: {
searchEnabled: true,
pageTitle: 'edge.edges'
},
ncyBreadcrumb: {
label: '{"icon": "transform", "label": "edge.edges"}'
}
});
}

75
ui/src/app/edge/edges.tpl.html

@ -0,0 +1,75 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tb-grid grid-configuration="vm.edgeGridConfig">
<details-buttons tb-help="'edges'" help-container-id="help-container">
<div id="help-container"></div>
</details-buttons>
<md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
id="tabs" md-border-bottom flex class="tb-absolute-fill">
<md-tab label="{{ 'edge.details' | translate }}">
<tb-edge edge="vm.grid.operatingItem()"
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
the-form="vm.grid.detailsForm"
on-delete-edge="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-edge>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('edge' | hasGenericPermission:'readAttributes')" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}">
<tb-attribute-table flex
readonly="!('edge' | hasGenericPermission:'writeAttributes')"
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.edge}}"
entity-name="vm.grid.operatingItem().name"
default-attribute-scope="{{vm.types.attributesScope.server.value}}">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('edge' | hasGenericPermission:'readTelemetry')" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.latest-telemetry' | translate }}">
<tb-attribute-table flex
readonly="!('edge' | hasGenericPermission:'writeTelemetry')"
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.edge}}"
entity-name="vm.grid.operatingItem().name"
default-attribute-scope="{{vm.types.latestTelemetry.value}}"
disable-attribute-scope-selection="true">
</tb-attribute-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('alarm' | hasGenericPermission:'read')" md-on-select="vm.grid.triggerResize()" label="{{ 'alarm.alarms' | translate }}">
<tb-alarm-table flex entity-type="vm.types.entityType.edge"
entity-id="vm.grid.operatingItem().id.id">
</tb-alarm-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'edge.events' | translate }}">
<tb-event-table flex entity-type="vm.types.entityType.edge"
entity-id="vm.grid.operatingItem().id.id"
tenant-id="vm.grid.operatingItem().tenantId.id"
default-event-type="{{vm.types.eventType.error.value}}">
</tb-event-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'relation.relations' | translate }}">
<tb-relation-table flex
readonly="!('edge' | hasGenericPermission:'write')"
entity-id="vm.grid.operatingItem().id.id"
entity-type="{{vm.types.entityType.edge}}">
</tb-relation-table>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && ('auditLog' | hasGenericPermission:'read')" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">
<tb-audit-log-table flex entity-type="vm.types.entityType.edge"
entity-id="vm.grid.operatingItem().id.id"
audit-log-mode="{{vm.types.auditLogMode.entity}}">
</tb-audit-log-table>
</md-tab>
</md-tabs>
</tb-grid>

25
ui/src/app/edge/index.js

@ -0,0 +1,25 @@
/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import EdgeRoutes from './edge.routes';
import {EdgeController, EdgeCardController} from './edge.controller';
import EdgeDirective from './edge.directive';
export default angular.module('thingsboard.edge', [])
.config(EdgeRoutes)
.controller('EdgeController', EdgeController)
.controller('EdgeCardController', EdgeCardController)
.directive('tbEdge', EdgeDirective)
.name;

6
ui/src/app/entity/entity-autocomplete.directive.js

@ -179,6 +179,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
scope.noEntitiesMatchingText = 'customer.no-customers-matching';
scope.entityRequiredText = 'customer.default-customer-required';
break;
case types.entityType.edge:
scope.selectEntityText = 'edge.select-edge';
scope.entityText = 'edge.edge';
scope.noEntitiesMatchingText = 'edge.no-edges-matching';
scope.entityRequiredText = 'edge.edge-required';
break;
}
if (scope.entity && scope.entity.id.entityType != scope.entityType) {
scope.entity = null;

1
ui/src/app/help/help-links.constant.js

@ -107,6 +107,7 @@ export default angular.module('thingsboard.help', [])
widgetsConfigRpc: helpBaseUrl + "/docs/user-guide/ui/dashboards#rpc",
widgetsConfigAlarm: helpBaseUrl + "/docs/user-guide/ui/dashboards#alarm",
widgetsConfigStatic: helpBaseUrl + "/docs/user-guide/ui/dashboards#static",
edges: helpBaseUrl + "/docs/user-guide/ui/edges"
},
getRuleNodeLink: function(ruleNode) {
if (ruleNode && ruleNode.component) {

4
ui/src/app/layout/index.js

@ -52,6 +52,7 @@ import thingsboardEntityView from '../entity-view';
import thingsboardWidgetLibrary from '../widget';
import thingsboardDashboard from '../dashboard';
import thingsboardRuleChain from '../rulechain';
import thingsboardEdge from '../edge';
import thingsboardJsonForm from '../jsonform';
@ -94,7 +95,8 @@ export default angular.module('thingsboard.home', [
thingsboardDashboardAutocomplete,
thingsboardKvMap,
thingsboardJsonObjectEdit,
thingsboardJsonContent
thingsboardJsonContent,
thingsboardEdge
])
.config(HomeRoutes)
.controller('HomeController', HomeController)

31
ui/src/app/locale/locale.constant-en_US.json

@ -705,6 +705,33 @@
"column": "Column",
"row": "Row"
},
"edge": {
"edge": "Edge",
"edges": "Edges",
"management": "Edge management",
"no-edges-matching": "No edges matching '{{entity}}' were found.",
"add": "Add Edge",
"view": "View Edge",
"no-edges-text": "No edges found",
"edge-details": "Edge details",
"add-edge-text": "Add new edge",
"delete": "Delete edge",
"delete-edges": "Delete edges",
"delete-edge-title": "Are you sure you want to delete the edge '{{edgeName}}'?",
"delete-edge-text": "Be careful, after the confirmation the edge and all related data will become unrecoverable.",
"delete-edges-title": "Are you sure you want to edge { count, plural, 1 {1 edge} other {# edges} }?",
"delete-edges-action-title": "Delete { count, plural, 1 {1 edge} other {# edges} }",
"delete-edges-text": "Be careful, after the confirmation all selected edges will be removed and all related data will become unrecoverable.",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"events": "Events",
"details": "Details",
"copyId": "Copy role Id",
"idCopiedMessage": "Edge Id has been copied to clipboard",
"permissions": "Permissions",
"edge-required": "Edge required",
},
"error": {
"unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
"unhandled-error-code": "Unhandled error code: {{errorCode}}",
@ -798,6 +825,10 @@
"type-rulenodes": "Rule nodes",
"list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }",
"rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'",
"type-edge": "Edge",
"type-edges": "Edges",
"list-of-edges": "{ count, plural, 1 {One edge} other {List of # edges} }",
"edge-name-starts-with": "Edges whose names start with '{{prefix}}'",
"type-current-customer": "Current Customer",
"search": "Search entities",
"selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",

32
ui/src/app/services/menu.service.js

@ -184,6 +184,12 @@ function Menu(userService, $state, $rootScope) {
state: 'home.entityViews',
icon: 'view_quilt'
},
{
name: 'edge.edges',
type: 'link',
state: 'home.edges',
icon: 'toys'
},
{
name: 'widget.widget-library',
type: 'link',
@ -254,6 +260,16 @@ function Menu(userService, $state, $rootScope) {
}
]
},
{
name: 'edge.management',
places: [
{
name: 'edge.edges',
icon: 'toys',
state: 'home.edges'
}
]
},
{
name: 'dashboard.management',
places: [
@ -306,6 +322,12 @@ function Menu(userService, $state, $rootScope) {
state: 'home.entityViews',
icon: 'view_quilt'
},
{
name: 'edge.edges',
type: 'link',
state: 'home.edges',
icon: 'toys'
},
{
name: 'dashboard.dashboards',
type: 'link',
@ -344,6 +366,16 @@ function Menu(userService, $state, $rootScope) {
}
]
},
{
name: 'edge.management',
places: [
{
name: 'edge.edges',
icon: 'toys',
state: 'home.edges'
}
]
},
{
name: 'dashboard.view-dashboards',
places: [

Loading…
Cancel
Save