Showing
24 changed files
with
552 additions
and
8 deletions
... | ... | @@ -89,6 +89,10 @@ |
89 | 89 | <artifactId>queue</artifactId> |
90 | 90 | </dependency> |
91 | 91 | <dependency> |
92 | + <groupId>org.thingsboard.common</groupId> | |
93 | + <artifactId>edge-api</artifactId> | |
94 | + </dependency> | |
95 | + <dependency> | |
92 | 96 | <groupId>org.thingsboard</groupId> |
93 | 97 | <artifactId>dao</artifactId> |
94 | 98 | <type>test-jar</type> | ... | ... |
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.service.edge.rpc; | |
17 | + | |
18 | +import com.google.common.io.Resources; | |
19 | +import io.grpc.Server; | |
20 | +import io.grpc.ServerBuilder; | |
21 | +import io.grpc.stub.StreamObserver; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.springframework.beans.factory.annotation.Value; | |
24 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
25 | +import org.springframework.stereotype.Service; | |
26 | +import org.thingsboard.server.common.edge.gen.EdgeProtos; | |
27 | +import org.thingsboard.server.common.edge.gen.EdgeRpcServiceGrpc; | |
28 | + | |
29 | +import javax.annotation.PostConstruct; | |
30 | +import javax.annotation.PreDestroy; | |
31 | +import java.io.File; | |
32 | +import java.io.IOException; | |
33 | + | |
34 | +@Service | |
35 | +@Slf4j | |
36 | +@ConditionalOnProperty(prefix = "edges.rpc", value = "enabled", havingValue = "true") | |
37 | +public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase { | |
38 | + | |
39 | + @Value("${edges.rpc.port}") | |
40 | + private int rpcPort; | |
41 | + @Value("${edges.rpc.ssl.enabled}") | |
42 | + private boolean sslEnabled; | |
43 | + @Value("${edges.rpc.ssl.cert}") | |
44 | + private String certFileResource; | |
45 | + @Value("${edges.rpc.ssl.privateKey}") | |
46 | + private String privateKeyResource; | |
47 | + | |
48 | + private Server server; | |
49 | + | |
50 | + @PostConstruct | |
51 | + public void init() { | |
52 | + log.info("Initializing Edge RPC service!"); | |
53 | + ServerBuilder builder = ServerBuilder.forPort(rpcPort).addService(this); | |
54 | + if (sslEnabled) { | |
55 | + try { | |
56 | + File certFile = new File(Resources.getResource(certFileResource).toURI()); | |
57 | + File privateKeyFile = new File(Resources.getResource(privateKeyResource).toURI()); | |
58 | + builder.useTransportSecurity(certFile, privateKeyFile); | |
59 | + } catch (Exception e) { | |
60 | + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e); | |
61 | + throw new RuntimeException("Unable to set up SSL context!", e); | |
62 | + } | |
63 | + } | |
64 | + server = builder.build(); | |
65 | + log.info("Going to start Edge RPC server using port: {}", rpcPort); | |
66 | + try { | |
67 | + server.start(); | |
68 | + } catch (IOException e) { | |
69 | + log.error("Failed to start Edge RPC server!", e); | |
70 | + throw new RuntimeException("Failed to start Edge RPC server!"); | |
71 | + } | |
72 | + log.info("Edge RPC service initialized!"); | |
73 | + } | |
74 | + | |
75 | + | |
76 | + @PreDestroy | |
77 | + public void destroy() { | |
78 | + if (server != null) { | |
79 | + server.shutdownNow(); | |
80 | + } | |
81 | + } | |
82 | + | |
83 | + @Override | |
84 | + public StreamObserver<EdgeProtos.UplinkMsg> sendUplink(StreamObserver<EdgeProtos.DownlinkMsg> responseObserver) { | |
85 | + log.info("sendUplink [{}]", responseObserver); | |
86 | + return new StreamObserver<EdgeProtos.UplinkMsg>() { | |
87 | + | |
88 | + @Override | |
89 | + public void onNext(EdgeProtos.UplinkMsg uplinkMsg) { | |
90 | + log.info("onNext [{}]", uplinkMsg); | |
91 | + } | |
92 | + | |
93 | + @Override | |
94 | + public void onError(Throwable throwable) { | |
95 | + log.info("onError", throwable); | |
96 | + } | |
97 | + | |
98 | + @Override | |
99 | + public void onCompleted() { | |
100 | + log.info("onCompleted"); | |
101 | + } | |
102 | + }; | |
103 | + } | |
104 | +} | ... | ... |
... | ... | @@ -495,6 +495,17 @@ transport: |
495 | 495 | bind_port: "${COAP_BIND_PORT:5683}" |
496 | 496 | timeout: "${COAP_TIMEOUT:10000}" |
497 | 497 | |
498 | +# Edges parameters | |
499 | +edges: | |
500 | + rpc: | |
501 | + enabled: "${EDGES_RPC_ENABLED:true}" | |
502 | + port: "${EDGES_RPC_PORT:60061}" | |
503 | + ssl: | |
504 | + # Enable/disable SSL support | |
505 | + enabled: "${EDGES_RPC_SSL_ENABLED:false}" | |
506 | + cert: "${EDGES_RPC_SSL_CERT:certChainFile.pem}" | |
507 | + privateKey: "${EDGES_RPC_SSL_PRIVATE_KEY:privateKeyFile.pem}" | |
508 | + | |
498 | 509 | swagger: |
499 | 510 | api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" |
500 | 511 | security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}" | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.page.TextPageData; |
26 | 26 | import org.thingsboard.server.common.data.page.TextPageLink; |
27 | 27 | |
28 | 28 | import java.util.List; |
29 | +import java.util.Optional; | |
29 | 30 | |
30 | 31 | public interface EdgeService { |
31 | 32 | |
... | ... | @@ -35,6 +36,8 @@ public interface EdgeService { |
35 | 36 | |
36 | 37 | Edge findEdgeByTenantIdAndName(TenantId tenantId, String name); |
37 | 38 | |
39 | + Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey); | |
40 | + | |
38 | 41 | Edge saveEdge(Edge edge); |
39 | 42 | |
40 | 43 | Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId); | ... | ... |
... | ... | @@ -44,6 +44,8 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H |
44 | 44 | private String name; |
45 | 45 | private String type; |
46 | 46 | private String label; |
47 | + private String routingKey; | |
48 | + private String secret; | |
47 | 49 | private transient JsonNode configuration; |
48 | 50 | |
49 | 51 | public Edge() { |
... | ... | @@ -60,6 +62,8 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H |
60 | 62 | this.customerId = edge.getCustomerId(); |
61 | 63 | this.type = edge.getType(); |
62 | 64 | this.name = edge.getName(); |
65 | + this.routingKey = edge.getRoutingKey(); | |
66 | + this.secret = edge.getSecret(); | |
63 | 67 | this.configuration = edge.getConfiguration(); |
64 | 68 | } |
65 | 69 | ... | ... |
common/edge-api/pom.xml
0 → 100644
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 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
20 | + <modelVersion>4.0.0</modelVersion> | |
21 | + <parent> | |
22 | + <groupId>org.thingsboard</groupId> | |
23 | + <version>2.4.1-SNAPSHOT</version> | |
24 | + <artifactId>common</artifactId> | |
25 | + </parent> | |
26 | + <groupId>org.thingsboard.common</groupId> | |
27 | + <artifactId>edge-api</artifactId> | |
28 | + <packaging>jar</packaging> | |
29 | + | |
30 | + <name>Thingsboard Server Remote Edge wrapper</name> | |
31 | + <url>https://thingsboard.io</url> | |
32 | + | |
33 | + <properties> | |
34 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
35 | + <main.dir>${basedir}/../..</main.dir> | |
36 | + </properties> | |
37 | + | |
38 | + <dependencies> | |
39 | + <dependency> | |
40 | + <groupId>org.thingsboard.common</groupId> | |
41 | + <artifactId>data</artifactId> | |
42 | + </dependency> | |
43 | + <dependency> | |
44 | + <groupId>org.thingsboard.common</groupId> | |
45 | + <artifactId>message</artifactId> | |
46 | + </dependency> | |
47 | + <dependency> | |
48 | + <groupId>com.google.code.gson</groupId> | |
49 | + <artifactId>gson</artifactId> | |
50 | + </dependency> | |
51 | + <dependency> | |
52 | + <groupId>org.slf4j</groupId> | |
53 | + <artifactId>slf4j-api</artifactId> | |
54 | + </dependency> | |
55 | + <dependency> | |
56 | + <groupId>org.slf4j</groupId> | |
57 | + <artifactId>log4j-over-slf4j</artifactId> | |
58 | + </dependency> | |
59 | + <dependency> | |
60 | + <groupId>ch.qos.logback</groupId> | |
61 | + <artifactId>logback-core</artifactId> | |
62 | + </dependency> | |
63 | + <dependency> | |
64 | + <groupId>ch.qos.logback</groupId> | |
65 | + <artifactId>logback-classic</artifactId> | |
66 | + </dependency> | |
67 | + <dependency> | |
68 | + <groupId>org.springframework</groupId> | |
69 | + <artifactId>spring-context</artifactId> | |
70 | + </dependency> | |
71 | + <dependency> | |
72 | + <groupId>org.springframework.boot</groupId> | |
73 | + <artifactId>spring-boot-starter-web</artifactId> | |
74 | + </dependency> | |
75 | + <dependency> | |
76 | + <groupId>io.netty</groupId> | |
77 | + <artifactId>netty-all</artifactId> | |
78 | + <scope>provided</scope> | |
79 | + </dependency> | |
80 | + <dependency> | |
81 | + <groupId>com.google.guava</groupId> | |
82 | + <artifactId>guava</artifactId> | |
83 | + </dependency> | |
84 | + <dependency> | |
85 | + <groupId>io.grpc</groupId> | |
86 | + <artifactId>grpc-netty</artifactId> | |
87 | + <exclusions> | |
88 | + <exclusion> | |
89 | + <artifactId>netty-transport</artifactId> | |
90 | + <groupId>io.netty</groupId> | |
91 | + </exclusion> | |
92 | + <exclusion> | |
93 | + <artifactId>netty-common</artifactId> | |
94 | + <groupId>io.netty</groupId> | |
95 | + </exclusion> | |
96 | + </exclusions> | |
97 | + </dependency> | |
98 | + <dependency> | |
99 | + <groupId>io.grpc</groupId> | |
100 | + <artifactId>grpc-protobuf</artifactId> | |
101 | + </dependency> | |
102 | + <dependency> | |
103 | + <groupId>io.grpc</groupId> | |
104 | + <artifactId>grpc-stub</artifactId> | |
105 | + </dependency> | |
106 | + <dependency> | |
107 | + <groupId>com.google.protobuf</groupId> | |
108 | + <artifactId>protobuf-java</artifactId> | |
109 | + </dependency> | |
110 | + </dependencies> | |
111 | + | |
112 | + <build> | |
113 | + <plugins> | |
114 | + <plugin> | |
115 | + <groupId>org.xolstice.maven.plugins</groupId> | |
116 | + <artifactId>protobuf-maven-plugin</artifactId> | |
117 | + </plugin> | |
118 | + </plugins> | |
119 | + </build> | |
120 | + | |
121 | + <distributionManagement> | |
122 | + <repository> | |
123 | + <id>thingsboard-repo-deploy</id> | |
124 | + <name>ThingsBoard Repo Deployment</name> | |
125 | + <url>https://repo.thingsboard.io/artifactory/libs-release-public</url> | |
126 | + </repository> | |
127 | + </distributionManagement> | |
128 | + | |
129 | +</project> | ... | ... |
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.edge.rpc; | |
17 | + | |
18 | +import com.google.common.io.Resources; | |
19 | +import io.grpc.ManagedChannel; | |
20 | +import io.grpc.netty.GrpcSslContexts; | |
21 | +import io.grpc.netty.NettyChannelBuilder; | |
22 | +import io.grpc.stub.StreamObserver; | |
23 | +import lombok.extern.slf4j.Slf4j; | |
24 | +import org.springframework.beans.factory.annotation.Value; | |
25 | +import org.springframework.stereotype.Service; | |
26 | +import org.thingsboard.server.common.edge.gen.EdgeProtos; | |
27 | +import org.thingsboard.server.common.edge.gen.EdgeRpcServiceGrpc; | |
28 | + | |
29 | +import javax.net.ssl.SSLException; | |
30 | +import java.io.File; | |
31 | +import java.net.URISyntaxException; | |
32 | + | |
33 | +@Service | |
34 | +@Slf4j | |
35 | +public class EdgeGrpcClient implements EdgeRpcClient { | |
36 | + | |
37 | + @Value("${cloud.rpc.host}") | |
38 | + private String rpcHost; | |
39 | + @Value("${cloud.rpc.port}") | |
40 | + private int rpcPort; | |
41 | + @Value("${cloud.rpc.timeout}") | |
42 | + private int timeoutSecs; | |
43 | + @Value("${cloud.rpc.ssl.enabled}") | |
44 | + private boolean sslEnabled; | |
45 | + @Value("${cloud.rpc.ssl.cert}") | |
46 | + private String certResource; | |
47 | + | |
48 | + private ManagedChannel channel; | |
49 | + | |
50 | + private StreamObserver<EdgeProtos.UplinkMsg> inputStream; | |
51 | + | |
52 | + @Override | |
53 | + public void connect() { | |
54 | + NettyChannelBuilder builder = NettyChannelBuilder.forAddress(rpcHost, rpcPort).usePlaintext(); | |
55 | + if (sslEnabled) { | |
56 | + try { | |
57 | + builder.sslContext(GrpcSslContexts.forClient().trustManager(new File(Resources.getResource(certResource).toURI())).build()); | |
58 | + } catch (URISyntaxException | SSLException e) { | |
59 | + log.error("Failed to initialize channel!", e); | |
60 | + throw new RuntimeException(e); | |
61 | + } | |
62 | + } | |
63 | + channel = builder.build(); | |
64 | + EdgeRpcServiceGrpc.EdgeRpcServiceStub stub = EdgeRpcServiceGrpc.newStub(channel); | |
65 | + StreamObserver<EdgeProtos.DownlinkMsg> responseObserver = new StreamObserver<EdgeProtos.DownlinkMsg>() { | |
66 | + @Override | |
67 | + public void onNext(EdgeProtos.DownlinkMsg downlinkMsg) { | |
68 | + log.info("onNext [{}]", downlinkMsg); | |
69 | + } | |
70 | + | |
71 | + @Override | |
72 | + public void onError(Throwable throwable) { | |
73 | + | |
74 | + } | |
75 | + | |
76 | + @Override | |
77 | + public void onCompleted() { | |
78 | + | |
79 | + } | |
80 | + }; | |
81 | + inputStream = stub.sendUplink(responseObserver); | |
82 | + inputStream.onNext(EdgeProtos.UplinkMsg.newBuilder().setMsgType(EdgeProtos.UplinkMsgType.DELETE_DEVICE_MESSAGE).build()); | |
83 | + } | |
84 | +} | ... | ... |
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.edge.rpc; | |
17 | + | |
18 | +public interface EdgeRpcClient { | |
19 | + | |
20 | + void connect(); | |
21 | +} | ... | ... |
common/edge-api/src/main/proto/edge.proto
0 → 100644
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 | +syntax = "proto3"; | |
17 | + | |
18 | +option java_package = "org.thingsboard.server.common.edge.gen"; | |
19 | +option java_outer_classname = "EdgeProtos"; | |
20 | + | |
21 | +package edge; | |
22 | + | |
23 | +// Interface exported by the ThingsBoard PRC Edge. | |
24 | +service EdgeRpcService { | |
25 | + | |
26 | + rpc sendUplink(stream UplinkMsg) returns (stream DownlinkMsg) {} | |
27 | + | |
28 | +} | |
29 | + | |
30 | +/** | |
31 | + * Data Structures; | |
32 | + */ | |
33 | +message UplinkMsg { | |
34 | + UplinkMsgType msgType = 1; | |
35 | +} | |
36 | + | |
37 | +message DownlinkMsg { | |
38 | + DownlinkMsgType msgType = 1; | |
39 | +} | |
40 | + | |
41 | +enum UplinkMsgType { | |
42 | + SAVE_DEVICE_MESSAGE = 0; | |
43 | + DELETE_DEVICE_MESSAGE = 1; | |
44 | +} | |
45 | + | |
46 | +enum DownlinkMsgType { | |
47 | + SAVE_ENTITY_MESSAGE = 0; | |
48 | + DELETE_ENTITY_MESSAGE = 1; | |
49 | +} | ... | ... |
... | ... | @@ -46,6 +46,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; |
46 | 46 | import org.thingsboard.server.dao.exception.DataValidationException; |
47 | 47 | import org.thingsboard.server.dao.service.DataValidator; |
48 | 48 | import org.thingsboard.server.dao.service.PaginatedRemover; |
49 | +import org.thingsboard.server.dao.service.Validator; | |
49 | 50 | import org.thingsboard.server.dao.tenant.TenantDao; |
50 | 51 | |
51 | 52 | import javax.annotation.Nullable; |
... | ... | @@ -111,6 +112,13 @@ public class BaseEdgeService extends AbstractEntityService implements EdgeServic |
111 | 112 | return edgeOpt.orElse(null); |
112 | 113 | } |
113 | 114 | |
115 | + @Override | |
116 | + public Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey) { | |
117 | + log.trace("Executing findEdgeByRoutingKey [{}]", routingKey); | |
118 | + Validator.validateString(routingKey, "Incorrect edge routingKey for search request."); | |
119 | + return edgeDao.findByRoutingKey(tenantId.getId(), routingKey); | |
120 | + } | |
121 | + | |
114 | 122 | @CacheEvict(cacheNames = EDGE_CACHE, key = "{#edge.tenantId, #edge.name}") |
115 | 123 | @Override |
116 | 124 | public Edge saveEdge(Edge edge) { | ... | ... |
... | ... | @@ -86,4 +86,9 @@ public class CassandraEdgeDao extends CassandraAbstractSearchTextDao<EdgeEntity, |
86 | 86 | public ListenableFuture<List<EntitySubtype>> findTenantEdgeTypesAsync(UUID tenantId) { |
87 | 87 | return null; |
88 | 88 | } |
89 | + | |
90 | + @Override | |
91 | + public Optional<Edge> findByRoutingKey(UUID tenantId, String routingKey) { | |
92 | + return Optional.empty(); | |
93 | + } | |
89 | 94 | } | ... | ... |
... | ... | @@ -116,5 +116,12 @@ public interface EdgeDao extends Dao<Edge> { |
116 | 116 | */ |
117 | 117 | ListenableFuture<List<EntitySubtype>> findTenantEdgeTypesAsync(UUID tenantId); |
118 | 118 | |
119 | + /** | |
120 | + * Find edge by routing Key. | |
121 | + * | |
122 | + * @param routingKey the edge routingKey | |
123 | + * @return the optional edge object | |
124 | + */ | |
125 | + Optional<Edge> findByRoutingKey(UUID tenantId, String routingKey); | |
119 | 126 | |
120 | 127 | } | ... | ... |
... | ... | @@ -361,7 +361,8 @@ public class ModelConstants { |
361 | 361 | public static final String EDGE_CONFIGURATION_PROPERTY = "configuration"; |
362 | 362 | public static final String EDGE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; |
363 | 363 | |
364 | - public static final String EDGE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "edge_by_tenant_and_search_text"; | |
364 | + public static final String EDGE_ROUTING_KEY_PROPERTY = "routing_key"; | |
365 | + public static final String EDGE_SECRET_PROPERTY = "secret"; | |
365 | 366 | |
366 | 367 | |
367 | 368 | /** | ... | ... |
... | ... | @@ -5,7 +5,7 @@ |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 6 | * You may obtain a copy of the License at |
7 | 7 | * |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | 9 | * |
10 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
... | ... | @@ -37,6 +37,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CONFIGURATION |
37 | 37 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY; |
38 | 38 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY; |
39 | 39 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY; |
40 | +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY; | |
41 | +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_SECRET_PROPERTY; | |
40 | 42 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY; |
41 | 43 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TYPE_PROPERTY; |
42 | 44 | import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; |
... | ... | @@ -70,6 +72,12 @@ public class EdgeEntity implements SearchTextEntity<Edge> { |
70 | 72 | @Column(name = SEARCH_TEXT_PROPERTY) |
71 | 73 | private String searchText; |
72 | 74 | |
75 | + @Column(name = EDGE_ROUTING_KEY_PROPERTY) | |
76 | + private String routingKey; | |
77 | + | |
78 | + @Column(name = EDGE_SECRET_PROPERTY) | |
79 | + private String secret; | |
80 | + | |
73 | 81 | @Column(name = EDGE_CONFIGURATION_PROPERTY, codec = JsonCodec.class) |
74 | 82 | private JsonNode configuration; |
75 | 83 | |
... | ... | @@ -90,6 +98,8 @@ public class EdgeEntity implements SearchTextEntity<Edge> { |
90 | 98 | this.type = edge.getType(); |
91 | 99 | this.name = edge.getName(); |
92 | 100 | this.label = edge.getLabel(); |
101 | + this.routingKey = edge.getRoutingKey(); | |
102 | + this.secret = edge.getSecret(); | |
93 | 103 | this.configuration = edge.getConfiguration(); |
94 | 104 | this.additionalInfo = edge.getAdditionalInfo(); |
95 | 105 | } |
... | ... | @@ -112,6 +122,8 @@ public class EdgeEntity implements SearchTextEntity<Edge> { |
112 | 122 | edge.setType(type); |
113 | 123 | edge.setName(name); |
114 | 124 | edge.setLabel(label); |
125 | + edge.setRoutingKey(routingKey); | |
126 | + edge.setSecret(secret); | |
115 | 127 | edge.setConfiguration(configuration); |
116 | 128 | edge.setAdditionalInfo(additionalInfo); |
117 | 129 | return edge; | ... | ... |
... | ... | @@ -5,7 +5,7 @@ |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 6 | * You may obtain a copy of the License at |
7 | 7 | * |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | 9 | * |
10 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
... | ... | @@ -35,11 +35,12 @@ import javax.persistence.Column; |
35 | 35 | import javax.persistence.Entity; |
36 | 36 | import javax.persistence.Table; |
37 | 37 | |
38 | -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY; | |
39 | 38 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_COLUMN_FAMILY_NAME; |
40 | 39 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY; |
41 | 40 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY; |
42 | 41 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY; |
42 | +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY; | |
43 | +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_SECRET_PROPERTY; | |
43 | 44 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TENANT_ID_PROPERTY; |
44 | 45 | import static org.thingsboard.server.dao.model.ModelConstants.EDGE_TYPE_PROPERTY; |
45 | 46 | import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; |
... | ... | @@ -69,6 +70,12 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity< |
69 | 70 | @Column(name = SEARCH_TEXT_PROPERTY) |
70 | 71 | private String searchText; |
71 | 72 | |
73 | + @Column(name = EDGE_ROUTING_KEY_PROPERTY) | |
74 | + private String routingKey; | |
75 | + | |
76 | + @Column(name = EDGE_SECRET_PROPERTY) | |
77 | + private String secret; | |
78 | + | |
72 | 79 | @Type(type = "json") |
73 | 80 | @Column(name = ModelConstants.EDGE_CONFIGURATION_PROPERTY) |
74 | 81 | private JsonNode configuration; |
... | ... | @@ -94,6 +101,8 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity< |
94 | 101 | this.type = edge.getType(); |
95 | 102 | this.name = edge.getName(); |
96 | 103 | this.label = edge.getLabel(); |
104 | + this.routingKey = edge.getRoutingKey(); | |
105 | + this.secret = edge.getSecret(); | |
97 | 106 | this.configuration = edge.getConfiguration(); |
98 | 107 | this.additionalInfo = edge.getAdditionalInfo(); |
99 | 108 | } |
... | ... | @@ -125,6 +134,8 @@ public class EdgeEntity extends BaseSqlEntity<Edge> implements SearchTextEntity< |
125 | 134 | edge.setType(type); |
126 | 135 | edge.setName(name); |
127 | 136 | edge.setLabel(label); |
137 | + edge.setRoutingKey(routingKey); | |
138 | + edge.setSecret(secret); | |
128 | 139 | edge.setConfiguration(configuration); |
129 | 140 | edge.setAdditionalInfo(additionalInfo); |
130 | 141 | return edge; | ... | ... |
... | ... | @@ -5,7 +5,7 @@ |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 6 | * You may obtain a copy of the License at |
7 | 7 | * |
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | 9 | * |
10 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
... | ... | @@ -126,6 +126,12 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao<EdgeEntity, Edge> imple |
126 | 126 | return service.submit(() -> convertTenantEdgeTypesToDto(tenantId, edgeRepository.findTenantEdgeTypes(fromTimeUUID(tenantId)))); |
127 | 127 | } |
128 | 128 | |
129 | + @Override | |
130 | + public Optional<Edge> findByRoutingKey(UUID tenantId, String routingKey) { | |
131 | + Edge edge = DaoUtil.getData(edgeRepository.findByRoutingKey(routingKey)); | |
132 | + return Optional.ofNullable(edge); | |
133 | + } | |
134 | + | |
129 | 135 | private List<EntitySubtype> convertTenantEdgeTypesToDto(UUID tenantId, List<String> types) { |
130 | 136 | List<EntitySubtype> list = Collections.emptyList(); |
131 | 137 | if (types != null && !types.isEmpty()) { | ... | ... |
... | ... | @@ -257,6 +257,8 @@ CREATE TABLE IF NOT EXISTS edge ( |
257 | 257 | type varchar(255), |
258 | 258 | name varchar(255), |
259 | 259 | label varchar(255), |
260 | + routing_key varchar(255), | |
261 | + secret varchar(255), | |
260 | 262 | search_text varchar(255), |
261 | 263 | tenant_id varchar(31) |
262 | 264 | ); |
\ No newline at end of file | ... | ... |
... | ... | @@ -407,6 +407,11 @@ |
407 | 407 | <version>${project.version}</version> |
408 | 408 | </dependency> |
409 | 409 | <dependency> |
410 | + <groupId>org.thingsboard.common</groupId> | |
411 | + <artifactId>edge-api</artifactId> | |
412 | + <version>${project.version}</version> | |
413 | + </dependency> | |
414 | + <dependency> | |
410 | 415 | <groupId>org.thingsboard</groupId> |
411 | 416 | <artifactId>dao</artifactId> |
412 | 417 | <version>${project.version}</version> | ... | ... |
... | ... | @@ -27,7 +27,7 @@ |
27 | 27 | |
28 | 28 | <div layout="row"> |
29 | 29 | <md-button ngclipboard data-clipboard-action="copy" |
30 | - ngclipboard-success="onEdgeIdCopied(e)" | |
30 | + ngclipboard-success="onEdgeIdCopied()" | |
31 | 31 | data-clipboard-text="{{edge.id.id}}" ng-show="!isEdit" |
32 | 32 | class="md-raised"> |
33 | 33 | <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> |
... | ... | @@ -69,4 +69,34 @@ |
69 | 69 | <textarea ng-model="edge.additionalInfo.description" rows="2"></textarea> |
70 | 70 | </md-input-container> |
71 | 71 | </fieldset> |
72 | + <div layout="row"> | |
73 | + <md-input-container class="md-block" flex> | |
74 | + <label translate>edge.edge-key</label> | |
75 | + <input ng-model="edge.routingKey" disabled> | |
76 | + </md-input-container> | |
77 | + <md-button class="md-icon-button" style="margin-top: 14px;" | |
78 | + ngclipboard data-clipboard-action="copy" | |
79 | + ngclipboard-success="onEdgeInfoCopied('key')" | |
80 | + data-clipboard-text="{{edge.routingKey}}"> | |
81 | + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> | |
82 | + <md-tooltip md-direction="top"> | |
83 | + {{ 'edge.copy-edge-key' | translate }} | |
84 | + </md-tooltip> | |
85 | + </md-button> | |
86 | + </div> | |
87 | + <div layout="row"> | |
88 | + <md-input-container class="md-block" flex> | |
89 | + <label translate>edge.edge-secret</label> | |
90 | + <input ng-model="edge.secret" disabled> | |
91 | + </md-input-container> | |
92 | + <md-button class="md-icon-button" style="margin-top: 14px;" | |
93 | + ngclipboard data-clipboard-action="copy" | |
94 | + data-clipboard-text="{{edge.secret}}" | |
95 | + ngclipboard-success="onEdgeInfoCopied('secret')"> | |
96 | + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> | |
97 | + <md-tooltip md-direction="top"> | |
98 | + {{ 'edge.copy-edge-secret' | translate }} | |
99 | + </md-tooltip> | |
100 | + </md-button> | |
101 | + </div> | |
72 | 102 | </md-content> | ... | ... |
... | ... | @@ -20,7 +20,7 @@ import edgeFieldsetTemplate from './edge-fieldset.tpl.html'; |
20 | 20 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 21 | |
22 | 22 | /*@ngInject*/ |
23 | -export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, toast, types, customerService) { | |
23 | +export default function EdgeDirective($compile, $templateCache, $translate, $mdDialog, $document, utils, toast, types, customerService) { | |
24 | 24 | var linker = function (scope, element) { |
25 | 25 | var template = $templateCache.get(edgeFieldsetTemplate); |
26 | 26 | element.html(template); |
... | ... | @@ -32,6 +32,10 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD |
32 | 32 | |
33 | 33 | scope.$watch('edge', function(newVal) { |
34 | 34 | if (newVal) { |
35 | + if (!scope.edge.id) { | |
36 | + scope.edge.routingKey = utils.guid(''); | |
37 | + scope.edge.secret = generateSecret(20); | |
38 | + } | |
35 | 39 | if (scope.edge.customerId && scope.edge.customerId.id !== types.id.nullUid) { |
36 | 40 | scope.isAssignedToCustomer = true; |
37 | 41 | customerService.getShortCustomerInfo(scope.edge.customerId.id).then( |
... | ... | @@ -48,12 +52,38 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD |
48 | 52 | } |
49 | 53 | }); |
50 | 54 | |
55 | + function generateSecret(length) { | |
56 | + if (angular.isUndefined(length) || length == null) { | |
57 | + length = 1; | |
58 | + } | |
59 | + var l = length > 10 ? 10 : length; | |
60 | + var str = Math.random().toString(36).substr(2, l); | |
61 | + if(str.length >= length){ | |
62 | + return str; | |
63 | + } | |
64 | + return str.concat(generateSecret(length - str.length)); | |
65 | + } | |
66 | + | |
51 | 67 | scope.onEdgeIdCopied = function() { |
52 | 68 | toast.showSuccess($translate.instant('edge.id-copied-message'), 750, angular.element(element).parent().parent(), 'bottom left'); |
53 | 69 | }; |
54 | 70 | |
55 | 71 | $compile(element.contents())(scope); |
56 | 72 | |
73 | + scope.onEdgeInfoCopied = function(type) { | |
74 | + let translateInstant = ""; | |
75 | + switch (type) { | |
76 | + case 'key': | |
77 | + translateInstant = "edge.edge-key-copied-message"; | |
78 | + break; | |
79 | + case 'secret': | |
80 | + translateInstant = "edge.edge-secret-copied-message"; | |
81 | + break; | |
82 | + } | |
83 | + toast.showSuccess($translate.instant(translateInstant), 750, angular.element(element).parent().parent(), 'top left'); | |
84 | + }; | |
85 | + | |
86 | + | |
57 | 87 | }; |
58 | 88 | return { |
59 | 89 | restrict: "E", | ... | ... |
... | ... | @@ -602,6 +602,16 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
602 | 602 | } |
603 | 603 | ); |
604 | 604 | return deferred.promise; |
605 | + case types.entityType.edge: | |
606 | + openImportDialogCSV($event, entityType, 'edge.import', 'edge.edge-file').then( | |
607 | + function success() { | |
608 | + deferred.resolve(); | |
609 | + }, | |
610 | + function fail() { | |
611 | + deferred.reject(); | |
612 | + } | |
613 | + ); | |
614 | + return deferred.promise; | |
605 | 615 | } |
606 | 616 | |
607 | 617 | } | ... | ... |
... | ... | @@ -777,7 +777,13 @@ |
777 | 777 | "unassign-from-edge": "Unassign from edge", |
778 | 778 | "dashboards": "Edge Dashboards", |
779 | 779 | "manage-edge-rulechains": "Manage edge rule chains", |
780 | - "rulechains": "Edge Rule Chains" | |
780 | + "rulechains": "Edge Rule Chains", | |
781 | + "edge-key": "Edge key", | |
782 | + "copy-edge-key": "Copy edge key", | |
783 | + "edge-key-copied-message": "Edge key has been copied to clipboard", | |
784 | + "edge-secret": "Edge secret", | |
785 | + "copy-edge-secret": "Copy edge secret", | |
786 | + "edge-secret-copied-message": "Edge secret has been copied to clipboard" | |
781 | 787 | }, |
782 | 788 | "error": { |
783 | 789 | "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", | ... | ... |