118 changed files with 5762 additions and 232 deletions
@ -0,0 +1,25 @@ |
|||||
|
{ |
||||
|
"widgetsBundle": { |
||||
|
"alias": "gateway_widgets", |
||||
|
"title": "Gateway widgets", |
||||
|
"image": null |
||||
|
}, |
||||
|
"widgetTypes": [ |
||||
|
{ |
||||
|
"alias": "extension_configuration_widget", |
||||
|
"name": "Extensions table", |
||||
|
"descriptor": { |
||||
|
"type": "latest", |
||||
|
"sizeX": 9, |
||||
|
"sizeY": 6.5, |
||||
|
"resources": [], |
||||
|
"templateHtml": "<tb-extensions-table-widget \n ctx=\"ctx\">\n</tb-extensions-table-widget>", |
||||
|
"templateCss": "#container {\n overflow: auto;\n}", |
||||
|
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.onResize = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1\n };\n}\n\nself.onDestroy = function() {\n}\n", |
||||
|
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"ExtensionTableSettings\",\n \"properties\": {\n \"extensionsTitle\": {\n \"title\": \"Extension table title\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"extensionsTitle\"\n ]\n}", |
||||
|
"dataKeySettingsSchema": "{}\n", |
||||
|
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<parent> |
||||
|
<artifactId>extensions</artifactId> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<version>1.4.0-SNAPSHOT</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<groupId>org.thingsboard.extensions</groupId> |
||||
|
<artifactId>extension-sns</artifactId> |
||||
|
<packaging>jar</packaging> |
||||
|
|
||||
|
<name>Thingsboard Server SNS Extension</name> |
||||
|
<url>https://thingsboard.io</url> |
||||
|
|
||||
|
<properties> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<main.dir>${basedir}/../..</main.dir> |
||||
|
<aws.sdk.version>1.11.229</aws.sdk.version> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<artifactId>extensions-api</artifactId> |
||||
|
<scope>provided</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<artifactId>extensions-core</artifactId> |
||||
|
<scope>provided</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.amazonaws</groupId> |
||||
|
<artifactId>aws-java-sdk-sns</artifactId> |
||||
|
<version>${aws.sdk.version}</version> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<artifactId>maven-assembly-plugin</artifactId> |
||||
|
<configuration> |
||||
|
<descriptors> |
||||
|
<descriptor>src/assembly/extension.xml</descriptor> |
||||
|
</descriptors> |
||||
|
</configuration> |
||||
|
<executions> |
||||
|
<execution> |
||||
|
<id>make-assembly</id> |
||||
|
<phase>package</phase> |
||||
|
<goals> |
||||
|
<goal>single</goal> |
||||
|
</goals> |
||||
|
</execution> |
||||
|
</executions> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,37 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> |
||||
|
<id>extension</id> |
||||
|
<formats> |
||||
|
<format>jar</format> |
||||
|
</formats> |
||||
|
<includeBaseDirectory>false</includeBaseDirectory> |
||||
|
<dependencySets> |
||||
|
<dependencySet> |
||||
|
<outputDirectory>/</outputDirectory> |
||||
|
<useProjectArtifact>true</useProjectArtifact> |
||||
|
<unpack>true</unpack> |
||||
|
<scope>runtime</scope> |
||||
|
<excludes> |
||||
|
|
||||
|
</excludes> |
||||
|
</dependencySet> |
||||
|
</dependencySets> |
||||
|
</assembly> |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.action; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/15/2017. |
||||
|
*/ |
||||
|
public class SnsTopicActionMsg extends AbstractRuleToPluginMsg<SnsTopicActionPayload> { |
||||
|
|
||||
|
public SnsTopicActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SnsTopicActionPayload payload) { |
||||
|
super(tenantId, customerId, deviceId, payload); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.action; |
||||
|
|
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.session.MsgType; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/15/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
@Builder |
||||
|
public class SnsTopicActionPayload implements Serializable { |
||||
|
|
||||
|
private final String topicArn; |
||||
|
private final String msgBody; |
||||
|
|
||||
|
private final Integer requestId; |
||||
|
private final MsgType msgType; |
||||
|
private final boolean sync; |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.action; |
||||
|
|
||||
|
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
||||
|
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; |
||||
|
import org.thingsboard.server.extensions.api.component.Action; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
||||
|
import org.thingsboard.server.extensions.api.rules.RuleContext; |
||||
|
import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/15/2017. |
||||
|
*/ |
||||
|
@Action(name = "SNS Topic Action", descriptor = "SnsTopicActionDescriptor.json", configuration = SnsTopicPluginActionConfiguration.class) |
||||
|
public class SnsTopicPluginAction extends AbstractTemplatePluginAction<SnsTopicPluginActionConfiguration> { |
||||
|
|
||||
|
@Override |
||||
|
protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { |
||||
|
SnsTopicActionPayload.SnsTopicActionPayloadBuilder builder = SnsTopicActionPayload.builder(); |
||||
|
builder.msgType(payload.getMsgType()); |
||||
|
builder.requestId(payload.getRequestId()); |
||||
|
builder.topicArn(configuration.getTopicArn()); |
||||
|
builder.msgBody(getMsgBody(ctx, msg)); |
||||
|
return Optional.of(new SnsTopicActionMsg(msg.getTenantId(), |
||||
|
msg.getCustomerId(), |
||||
|
msg.getDeviceId(), |
||||
|
builder.build())); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.action; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/15/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
public class SnsTopicPluginActionConfiguration implements TemplateActionConfiguration { |
||||
|
|
||||
|
private String topicArn; |
||||
|
private String template; |
||||
|
private boolean sync; |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.plugin; |
||||
|
|
||||
|
import com.amazonaws.services.sns.AmazonSNS; |
||||
|
import com.amazonaws.services.sns.model.PublishRequest; |
||||
|
import com.amazonaws.services.sns.model.PublishResult; |
||||
|
import com.amazonaws.services.sqs.AmazonSQS; |
||||
|
import com.amazonaws.services.sqs.model.SendMessageRequest; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.id.RuleId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; |
||||
|
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
||||
|
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
||||
|
import org.thingsboard.server.extensions.api.rules.RuleException; |
||||
|
import org.thingsboard.server.extensions.sns.action.SnsTopicActionMsg; |
||||
|
import org.thingsboard.server.extensions.sns.action.SnsTopicActionPayload; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/6/2017. |
||||
|
*/ |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
public class SnsMessageHandler implements RuleMsgHandler { |
||||
|
|
||||
|
private final AmazonSNS sns; |
||||
|
|
||||
|
@Override |
||||
|
public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException { |
||||
|
if (msg instanceof SnsTopicActionMsg) { |
||||
|
SnsTopicActionPayload payload = ((SnsTopicActionMsg) msg).getPayload(); |
||||
|
PublishRequest publishRequest = new PublishRequest() |
||||
|
.withTopicArn(payload.getTopicArn()) |
||||
|
.withMessage(payload.getMsgBody()); |
||||
|
sns.publish(publishRequest); |
||||
|
if (payload.isSync()) { |
||||
|
ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, |
||||
|
BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!"); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.plugin; |
||||
|
|
||||
|
import com.amazonaws.auth.AWSCredentials; |
||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider; |
||||
|
import com.amazonaws.auth.BasicAWSCredentials; |
||||
|
import com.amazonaws.services.sns.AmazonSNS; |
||||
|
import com.amazonaws.services.sns.AmazonSNSClient; |
||||
|
import org.thingsboard.server.extensions.api.component.Plugin; |
||||
|
import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; |
||||
|
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
||||
|
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; |
||||
|
import org.thingsboard.server.extensions.sns.action.SnsTopicPluginAction; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/15/2017. |
||||
|
*/ |
||||
|
@Plugin(name = "SNS Plugin", actions = {SnsTopicPluginAction.class}, |
||||
|
descriptor = "SnsPluginDescriptor.json", configuration = SnsPluginConfiguration.class) |
||||
|
public class SnsPlugin extends AbstractPlugin<SnsPluginConfiguration> { |
||||
|
|
||||
|
private SnsMessageHandler snsMessageHandler; |
||||
|
private SnsPluginConfiguration configuration; |
||||
|
|
||||
|
@Override |
||||
|
public void init(SnsPluginConfiguration configuration) { |
||||
|
this.configuration = configuration; |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
private void init() { |
||||
|
AWSCredentials awsCredentials = new BasicAWSCredentials(configuration.getAccessKeyId(), configuration.getSecretAccessKey()); |
||||
|
AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); |
||||
|
AmazonSNS sns = AmazonSNSClient.builder() |
||||
|
.withCredentials(credProvider) |
||||
|
.withRegion(configuration.getRegion()) |
||||
|
.build(); |
||||
|
this.snsMessageHandler = new SnsMessageHandler(sns); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void destroy() { |
||||
|
this.snsMessageHandler = null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleMsgHandler getRuleMsgHandler() { |
||||
|
return snsMessageHandler; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void resume(PluginContext ctx) { |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void suspend(PluginContext ctx) { |
||||
|
destroy(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void stop(PluginContext ctx) { |
||||
|
destroy(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sns.plugin; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/5/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
public class SnsPluginConfiguration { |
||||
|
|
||||
|
private String accessKeyId; |
||||
|
private String secretAccessKey; |
||||
|
private String region; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
{ |
||||
|
"schema": { |
||||
|
"title": "SNS Plugin Configuration", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"accessKeyId": { |
||||
|
"title": "Access Key ID", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"secretAccessKey": { |
||||
|
"title": "Secret Access Key", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"region": { |
||||
|
"title": "Region", |
||||
|
"type": "string" |
||||
|
} |
||||
|
}, |
||||
|
"required": [ |
||||
|
"accessKeyId", |
||||
|
"secretAccessKey", |
||||
|
"region" |
||||
|
] |
||||
|
}, |
||||
|
"form": [ |
||||
|
"accessKeyId", |
||||
|
"secretAccessKey", |
||||
|
"region" |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
{ |
||||
|
"schema": { |
||||
|
"title": "SNS Topic Action Configuration", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"sync": { |
||||
|
"title": "Requires delivery confirmation", |
||||
|
"type": "boolean" |
||||
|
}, |
||||
|
"topicArn": { |
||||
|
"title": "Topic ARN", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"template": { |
||||
|
"title": "Body Template", |
||||
|
"type": "string" |
||||
|
} |
||||
|
}, |
||||
|
"required": [ |
||||
|
"sync", |
||||
|
"topicArn", |
||||
|
"template" |
||||
|
] |
||||
|
}, |
||||
|
"form": [ |
||||
|
"sync", |
||||
|
"topicArn", |
||||
|
{ |
||||
|
"key": "template", |
||||
|
"type": "textarea", |
||||
|
"rows": 5 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<parent> |
||||
|
<artifactId>extensions</artifactId> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<version>1.4.0-SNAPSHOT</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<groupId>org.thingsboard.extensions</groupId> |
||||
|
<artifactId>extension-sqs</artifactId> |
||||
|
<packaging>jar</packaging> |
||||
|
|
||||
|
<name>Thingsboard Server SQS Extension</name> |
||||
|
<url>https://thingsboard.io</url> |
||||
|
|
||||
|
<properties> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<main.dir>${basedir}/../..</main.dir> |
||||
|
<aws.sdk.version>1.11.229</aws.sdk.version> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<artifactId>extensions-api</artifactId> |
||||
|
<scope>provided</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<artifactId>extensions-core</artifactId> |
||||
|
<scope>provided</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.amazonaws</groupId> |
||||
|
<artifactId>aws-java-sdk-sqs</artifactId> |
||||
|
<version>${aws.sdk.version}</version> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<artifactId>maven-assembly-plugin</artifactId> |
||||
|
<configuration> |
||||
|
<descriptors> |
||||
|
<descriptor>src/assembly/extension.xml</descriptor> |
||||
|
</descriptors> |
||||
|
</configuration> |
||||
|
<executions> |
||||
|
<execution> |
||||
|
<id>make-assembly</id> |
||||
|
<phase>package</phase> |
||||
|
<goals> |
||||
|
<goal>single</goal> |
||||
|
</goals> |
||||
|
</execution> |
||||
|
</executions> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,37 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd"> |
||||
|
<id>extension</id> |
||||
|
<formats> |
||||
|
<format>jar</format> |
||||
|
</formats> |
||||
|
<includeBaseDirectory>false</includeBaseDirectory> |
||||
|
<dependencySets> |
||||
|
<dependencySet> |
||||
|
<outputDirectory>/</outputDirectory> |
||||
|
<useProjectArtifact>true</useProjectArtifact> |
||||
|
<unpack>true</unpack> |
||||
|
<scope>runtime</scope> |
||||
|
<excludes> |
||||
|
|
||||
|
</excludes> |
||||
|
</dependencySet> |
||||
|
</dependencySets> |
||||
|
</assembly> |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.fifo; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/10/2017. |
||||
|
*/ |
||||
|
public class SqsFifoQueueActionMsg extends AbstractRuleToPluginMsg<SqsFifoQueueActionPayload> { |
||||
|
|
||||
|
public SqsFifoQueueActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SqsFifoQueueActionPayload payload) { |
||||
|
super(tenantId, customerId, deviceId, payload); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.fifo; |
||||
|
|
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.session.MsgType; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/10/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
@Builder |
||||
|
public class SqsFifoQueueActionPayload implements Serializable { |
||||
|
|
||||
|
private final String queue; |
||||
|
private final String msgBody; |
||||
|
private final String deviceId; |
||||
|
|
||||
|
private final Integer requestId; |
||||
|
private final MsgType msgType; |
||||
|
private final boolean sync; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.fifo; |
||||
|
|
||||
|
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
||||
|
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; |
||||
|
import org.thingsboard.server.extensions.api.component.Action; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
||||
|
import org.thingsboard.server.extensions.api.rules.RuleContext; |
||||
|
import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; |
||||
|
import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; |
||||
|
import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; |
||||
|
import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginActionConfiguration; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/5/2017. |
||||
|
*/ |
||||
|
@Action(name = "SQS Fifo Queue Action", descriptor = "SqsFifoQueueActionDescriptor.json", configuration = SqsFifoQueuePluginActionConfiguration.class) |
||||
|
public class SqsFifoQueuePluginAction extends AbstractTemplatePluginAction<SqsFifoQueuePluginActionConfiguration> { |
||||
|
|
||||
|
@Override |
||||
|
protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { |
||||
|
SqsFifoQueueActionPayload.SqsFifoQueueActionPayloadBuilder builder = SqsFifoQueueActionPayload.builder(); |
||||
|
builder.msgType(payload.getMsgType()); |
||||
|
builder.requestId(payload.getRequestId()); |
||||
|
builder.queue(configuration.getQueue()); |
||||
|
builder.deviceId(msg.getDeviceId().toString()); |
||||
|
builder.msgBody(getMsgBody(ctx, msg)); |
||||
|
return Optional.of(new SqsFifoQueueActionMsg(msg.getTenantId(), |
||||
|
msg.getCustomerId(), |
||||
|
msg.getDeviceId(), |
||||
|
builder.build())); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.fifo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/10/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
public class SqsFifoQueuePluginActionConfiguration implements TemplateActionConfiguration { |
||||
|
|
||||
|
private String queue; |
||||
|
private String template; |
||||
|
private boolean sync; |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.standard; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/6/2017. |
||||
|
*/ |
||||
|
public class SqsStandardQueueActionMsg extends AbstractRuleToPluginMsg<SqsStandardQueueActionPayload> { |
||||
|
|
||||
|
public SqsStandardQueueActionMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, SqsStandardQueueActionPayload payload) { |
||||
|
super(tenantId, customerId, deviceId, payload); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.standard; |
||||
|
|
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.session.MsgType; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/6/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
@Builder |
||||
|
public class SqsStandardQueueActionPayload implements Serializable { |
||||
|
|
||||
|
private final String queue; |
||||
|
private final String msgBody; |
||||
|
private final int delaySeconds; |
||||
|
|
||||
|
private final Integer requestId; |
||||
|
private final MsgType msgType; |
||||
|
private final boolean sync; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.standard; |
||||
|
|
||||
|
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
||||
|
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; |
||||
|
import org.thingsboard.server.extensions.api.component.Action; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
||||
|
import org.thingsboard.server.extensions.api.rules.RuleContext; |
||||
|
import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/5/2017. |
||||
|
*/ |
||||
|
@Action(name = "SQS Standard Queue Action", descriptor = "SqsStandardQueueActionDescriptor.json", configuration = SqsStandardQueuePluginActionConfiguration.class) |
||||
|
public class SqsStandardQueuePluginAction extends AbstractTemplatePluginAction<SqsStandardQueuePluginActionConfiguration> { |
||||
|
|
||||
|
@Override |
||||
|
protected Optional<RuleToPluginMsg> buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { |
||||
|
SqsStandardQueueActionPayload.SqsStandardQueueActionPayloadBuilder builder = SqsStandardQueueActionPayload.builder(); |
||||
|
builder.msgType(payload.getMsgType()); |
||||
|
builder.requestId(payload.getRequestId()); |
||||
|
builder.queue(configuration.getQueue()); |
||||
|
builder.delaySeconds(configuration.getDelaySeconds()); |
||||
|
builder.msgBody(getMsgBody(ctx, msg)); |
||||
|
return Optional.of(new SqsStandardQueueActionMsg(msg.getTenantId(), |
||||
|
msg.getCustomerId(), |
||||
|
msg.getDeviceId(), |
||||
|
builder.build())); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.action.standard; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.extensions.core.action.template.TemplateActionConfiguration; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/6/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
public class SqsStandardQueuePluginActionConfiguration implements TemplateActionConfiguration { |
||||
|
|
||||
|
private String queue; |
||||
|
private int delaySeconds; |
||||
|
private boolean sync; |
||||
|
private String template; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.plugin; |
||||
|
|
||||
|
import com.amazonaws.services.sqs.AmazonSQS; |
||||
|
import com.amazonaws.services.sqs.model.SendMessageRequest; |
||||
|
import com.amazonaws.services.sqs.model.SendMessageResult; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.id.RuleId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; |
||||
|
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
||||
|
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.AbstractRuleToPluginMsg; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; |
||||
|
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
||||
|
import org.thingsboard.server.extensions.api.rules.RuleException; |
||||
|
import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueueActionMsg; |
||||
|
import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueueActionPayload; |
||||
|
import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; |
||||
|
import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/15/2017. |
||||
|
*/ |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
public class SqsMessageHandler implements RuleMsgHandler { |
||||
|
|
||||
|
private final AmazonSQS sqs; |
||||
|
|
||||
|
@Override |
||||
|
public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) throws RuleException { |
||||
|
if (msg instanceof SqsStandardQueueActionMsg) { |
||||
|
sendMessageToStandardQueue(ctx, tenantId, ruleId, msg); |
||||
|
return; |
||||
|
} |
||||
|
if (msg instanceof SqsFifoQueueActionMsg) { |
||||
|
sendMessageToFifoQueue(ctx, tenantId, ruleId, msg); |
||||
|
return; |
||||
|
} |
||||
|
throw new RuleException("Unsupported message type " + msg.getClass().getName() + "!"); |
||||
|
} |
||||
|
|
||||
|
private void sendMessageToStandardQueue(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) { |
||||
|
SqsStandardQueueActionPayload payload = ((SqsStandardQueueActionMsg) msg).getPayload(); |
||||
|
SendMessageRequest sendMsgRequest = new SendMessageRequest() |
||||
|
.withDelaySeconds(payload.getDelaySeconds()) |
||||
|
.withQueueUrl(payload.getQueue()) |
||||
|
.withMessageBody(payload.getMsgBody()); |
||||
|
sqs.sendMessage(sendMsgRequest); |
||||
|
if (payload.isSync()) { |
||||
|
ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, |
||||
|
BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void sendMessageToFifoQueue(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg<?> msg) { |
||||
|
SqsFifoQueueActionPayload payload = ((SqsFifoQueueActionMsg) msg).getPayload(); |
||||
|
SendMessageRequest sendMsgRequest = new SendMessageRequest() |
||||
|
.withQueueUrl(payload.getQueue()) |
||||
|
.withMessageBody(payload.getMsgBody()) |
||||
|
.withMessageGroupId(payload.getDeviceId()); |
||||
|
sqs.sendMessage(sendMsgRequest); |
||||
|
if (payload.isSync()) { |
||||
|
ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, |
||||
|
BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.plugin; |
||||
|
|
||||
|
import com.amazonaws.auth.AWSCredentials; |
||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider; |
||||
|
import com.amazonaws.auth.BasicAWSCredentials; |
||||
|
import com.amazonaws.regions.Regions; |
||||
|
import com.amazonaws.services.sqs.AmazonSQS; |
||||
|
import com.amazonaws.services.sqs.AmazonSQSClientBuilder; |
||||
|
import org.thingsboard.server.extensions.api.component.Plugin; |
||||
|
import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; |
||||
|
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
||||
|
import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; |
||||
|
import org.thingsboard.server.extensions.sqs.action.fifo.SqsFifoQueuePluginAction; |
||||
|
import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginAction; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/6/2017. |
||||
|
*/ |
||||
|
@Plugin(name = "SQS Plugin", actions = {SqsStandardQueuePluginAction.class, SqsFifoQueuePluginAction.class}, |
||||
|
descriptor = "SqsPluginDescriptor.json", configuration = SqsPluginConfiguration.class) |
||||
|
public class SqsPlugin extends AbstractPlugin<SqsPluginConfiguration> { |
||||
|
|
||||
|
private SqsMessageHandler sqsMessageHandler; |
||||
|
private SqsPluginConfiguration configuration; |
||||
|
|
||||
|
@Override |
||||
|
public void init(SqsPluginConfiguration configuration) { |
||||
|
this.configuration = configuration; |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
private void init() { |
||||
|
AWSCredentials awsCredentials = new BasicAWSCredentials(configuration.getAccessKeyId(), configuration.getSecretAccessKey()); |
||||
|
AmazonSQS sqs = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) |
||||
|
.withRegion(Regions.fromName(configuration.getRegion())).build(); |
||||
|
this.sqsMessageHandler = new SqsMessageHandler(sqs); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void destroy() { |
||||
|
this.sqsMessageHandler = null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected RuleMsgHandler getRuleMsgHandler() { |
||||
|
return sqsMessageHandler; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void resume(PluginContext ctx) { |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void suspend(PluginContext ctx) { |
||||
|
destroy(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void stop(PluginContext ctx) { |
||||
|
destroy(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs.plugin; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/5/2017. |
||||
|
*/ |
||||
|
@Data |
||||
|
public class SqsPluginConfiguration { |
||||
|
|
||||
|
private String accessKeyId; |
||||
|
private String secretAccessKey; |
||||
|
private String region; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
{ |
||||
|
"schema": { |
||||
|
"title": "SQS FIFO Queue Action Configuration", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"sync": { |
||||
|
"title": "Requires delivery confirmation", |
||||
|
"type": "boolean" |
||||
|
}, |
||||
|
"queue": { |
||||
|
"title": "Queue URL", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"template": { |
||||
|
"title": "Body Template", |
||||
|
"type": "string" |
||||
|
} |
||||
|
}, |
||||
|
"required": [ |
||||
|
"sync", |
||||
|
"queue", |
||||
|
"template" |
||||
|
] |
||||
|
}, |
||||
|
"form": [ |
||||
|
"sync", |
||||
|
"queue", |
||||
|
{ |
||||
|
"key": "template", |
||||
|
"type": "textarea", |
||||
|
"rows": 5 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
{ |
||||
|
"schema": { |
||||
|
"title": "SQS Plugin Configuration", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"accessKeyId": { |
||||
|
"title": "Access Key ID", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"secretAccessKey": { |
||||
|
"title": "Secret Access Key", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"region": { |
||||
|
"title": "Region", |
||||
|
"type": "string" |
||||
|
} |
||||
|
}, |
||||
|
"required": [ |
||||
|
"accessKeyId", |
||||
|
"secretAccessKey", |
||||
|
"region" |
||||
|
] |
||||
|
}, |
||||
|
"form": [ |
||||
|
"accessKeyId", |
||||
|
"secretAccessKey", |
||||
|
"region" |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
{ |
||||
|
"schema": { |
||||
|
"title": "SQS Standard Queue Action Configuration", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"sync": { |
||||
|
"title": "Requires delivery confirmation", |
||||
|
"type": "boolean" |
||||
|
}, |
||||
|
"queue": { |
||||
|
"title": "Queue URL", |
||||
|
"type": "string" |
||||
|
}, |
||||
|
"delaySeconds": { |
||||
|
"title": "Delay Seconds", |
||||
|
"type": "integer", |
||||
|
"default": 0 |
||||
|
}, |
||||
|
"template": { |
||||
|
"title": "Body Template", |
||||
|
"type": "string" |
||||
|
} |
||||
|
}, |
||||
|
"required": [ |
||||
|
"sync", |
||||
|
"queue", |
||||
|
"delaySeconds", |
||||
|
"template" |
||||
|
] |
||||
|
}, |
||||
|
"form": [ |
||||
|
"sync", |
||||
|
"queue", |
||||
|
"delaySeconds", |
||||
|
{ |
||||
|
"key": "template", |
||||
|
"type": "textarea", |
||||
|
"rows": 5 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.extensions.sqs; |
||||
|
|
||||
|
import com.amazonaws.auth.AWSCredentials; |
||||
|
import com.amazonaws.auth.AWSStaticCredentialsProvider; |
||||
|
import com.amazonaws.auth.BasicAWSCredentials; |
||||
|
import com.amazonaws.regions.Regions; |
||||
|
import com.amazonaws.services.sqs.AmazonSQS; |
||||
|
import com.amazonaws.services.sqs.AmazonSQSClientBuilder; |
||||
|
import com.amazonaws.services.sqs.model.DeleteMessageRequest; |
||||
|
import com.amazonaws.services.sqs.model.Message; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* Created by Valerii Sosliuk on 11/10/2017. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class SqsDemoClient { |
||||
|
|
||||
|
private static final String ACCESS_KEY_ID = "$ACCES_KEY_ID"; |
||||
|
private static final String SECRET_ACCESS_KEY = "$SECRET_ACCESS_KEY"; |
||||
|
|
||||
|
private static final String QUEUE_URL = "$QUEUE_URL"; |
||||
|
private static final String REGION = "us-east-1"; |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
log.info("Starting SQS Demo Clinent..."); |
||||
|
AWSCredentials awsCredentials = new BasicAWSCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY); |
||||
|
AmazonSQS sqs = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) |
||||
|
.withRegion(Regions.fromName(REGION)).build(); |
||||
|
SqsDemoClient client = new SqsDemoClient(); |
||||
|
client.pollMessages(sqs); |
||||
|
} |
||||
|
|
||||
|
private void pollMessages(AmazonSQS sqs) { |
||||
|
log.info("Polling messages"); |
||||
|
while (true) { |
||||
|
List<Message> messages = sqs.receiveMessage(QUEUE_URL).getMessages(); |
||||
|
messages.forEach(m -> { |
||||
|
log.info("Message Received: " + m.getBody()); |
||||
|
System.out.println(m.getBody()); |
||||
|
DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest(QUEUE_URL, m.getReceiptHandle()); |
||||
|
sqs.deleteMessage(deleteMessageRequest); |
||||
|
}); |
||||
|
try { |
||||
|
Thread.sleep(1000); |
||||
|
} catch (InterruptedException e) { |
||||
|
Thread.currentThread().interrupt(); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
<configuration> |
||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
||||
|
<encoder> |
||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> |
||||
|
</encoder> |
||||
|
</appender> |
||||
|
<root level="INFO"> |
||||
|
<appender-ref ref="STDOUT"/> |
||||
|
</root> |
||||
|
</configuration> |
||||
@ -0,0 +1,18 @@ |
|||||
|
@REM |
||||
|
@REM Copyright © 2016-2017 The Thingsboard Authors |
||||
|
@REM |
||||
|
@REM Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
@REM you may not use this file except in compliance with the License. |
||||
|
@REM You may obtain a copy of the License at |
||||
|
@REM |
||||
|
@REM http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
@REM |
||||
|
@REM Unless required by applicable law or agreed to in writing, software |
||||
|
@REM distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
@REM See the License for the specific language governing permissions and |
||||
|
@REM limitations under the License. |
||||
|
@REM |
||||
|
|
||||
|
mvn clean install -rf :application |
||||
|
|
||||
@ -0,0 +1,36 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
# |
||||
|
# Copyright © 2016-2017 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 paho.mqtt.client as mqtt |
||||
|
from time import sleep |
||||
|
import random |
||||
|
|
||||
|
broker="test.mosquitto.org" |
||||
|
topic_pub='v1/devices/me/telemetry' |
||||
|
|
||||
|
|
||||
|
client = mqtt.Client() |
||||
|
|
||||
|
client.username_pw_set("TEST_TOKEN") |
||||
|
client.connect('127.0.0.1', 1883, 1) |
||||
|
|
||||
|
for i in range(5): |
||||
|
x = random.randrange(20, 100) |
||||
|
print x |
||||
|
msg = '{"windSpeed":"'+ str(x) + '"}' |
||||
|
client.publish(topic_pub, msg) |
||||
|
sleep(0.1) |
||||
@ -0,0 +1,298 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 beautify from 'js-beautify'; |
||||
|
|
||||
|
const js_beautify = beautify.js; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ExtensionDialogController($scope, $mdDialog, $translate, isAdd, allExtensions, entityId, entityType, extension, types, attributeService) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.types = types; |
||||
|
vm.isAdd = isAdd; |
||||
|
vm.entityType = entityType; |
||||
|
vm.entityId = entityId; |
||||
|
vm.allExtensions = allExtensions; |
||||
|
|
||||
|
|
||||
|
if (extension) { |
||||
|
vm.extension = angular.copy(extension); |
||||
|
editTransformers(vm.extension); |
||||
|
} else { |
||||
|
vm.extension = {}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
vm.extensionTypeChange = function () { |
||||
|
|
||||
|
if (vm.extension.type === "HTTP") { |
||||
|
vm.extension.configuration = { |
||||
|
"converterConfigurations": [] |
||||
|
}; |
||||
|
} |
||||
|
if (vm.extension.type === "MQTT") { |
||||
|
vm.extension.configuration = { |
||||
|
"brokers": [] |
||||
|
}; |
||||
|
} |
||||
|
if (vm.extension.type === "OPC UA") { |
||||
|
vm.extension.configuration = { |
||||
|
"servers": [] |
||||
|
}; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
vm.cancel = cancel; |
||||
|
function cancel() { |
||||
|
$mdDialog.cancel(); |
||||
|
} |
||||
|
|
||||
|
vm.save = save; |
||||
|
function save() { |
||||
|
let $errorElement = angular.element('[name=theForm]').find('.ng-invalid'); |
||||
|
|
||||
|
if ($errorElement.length) { |
||||
|
|
||||
|
let $mdDialogScroll = angular.element('md-dialog-content').scrollTop(); |
||||
|
let $mdDialogTop = angular.element('md-dialog-content').offset().top; |
||||
|
let $errorElementTop = angular.element('[name=theForm]').find('.ng-invalid').eq(0).offset().top; |
||||
|
|
||||
|
|
||||
|
if ($errorElementTop !== $mdDialogTop) { |
||||
|
angular.element('md-dialog-content').animate({ |
||||
|
scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 50 |
||||
|
}, 500); |
||||
|
$errorElement.eq(0).focus(); |
||||
|
} |
||||
|
} else { |
||||
|
|
||||
|
if(vm.isAdd) { |
||||
|
vm.allExtensions.push(vm.extension); |
||||
|
} else { |
||||
|
var index = vm.allExtensions.indexOf(extension); |
||||
|
if(index > -1) { |
||||
|
vm.allExtensions[index] = vm.extension; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$mdDialog.hide(); |
||||
|
saveTransformers(); |
||||
|
|
||||
|
var editedValue = angular.toJson(vm.allExtensions); |
||||
|
|
||||
|
attributeService |
||||
|
.saveEntityAttributes( |
||||
|
vm.entityType, |
||||
|
vm.entityId, |
||||
|
types.attributesScope.shared.value, |
||||
|
[{key:"configuration", value:editedValue}] |
||||
|
) |
||||
|
.then(function success() { |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
vm.validateId = function() { |
||||
|
var coincidenceArray = vm.allExtensions.filter(function(ext) { |
||||
|
return ext.id == vm.extension.id; |
||||
|
}); |
||||
|
if(coincidenceArray.length) { |
||||
|
if(!vm.isAdd) { |
||||
|
if(coincidenceArray[0].id == extension.id) { |
||||
|
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); |
||||
|
} else { |
||||
|
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', false); |
||||
|
} |
||||
|
} else { |
||||
|
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', false); |
||||
|
} |
||||
|
} else { |
||||
|
$scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
function saveTransformers() { |
||||
|
if(vm.extension.type == types.extensionType.http) { |
||||
|
var config = vm.extension.configuration.converterConfigurations; |
||||
|
if(config && config.length > 0) { |
||||
|
for(let i=0;i<config.length;i++) { |
||||
|
for(let j=0;j<config[i].converters.length;j++){ |
||||
|
for(let k=0;k<config[i].converters[j].attributes.length;k++){ |
||||
|
if(config[i].converters[j].attributes[k].transformerType == "toDouble"){ |
||||
|
config[i].converters[j].attributes[k].transformer = {type: "intToDouble"}; |
||||
|
} |
||||
|
delete config[i].converters[j].attributes[k].transformerType; |
||||
|
} |
||||
|
for(let l=0;l<config[i].converters[j].timeseries.length;l++) { |
||||
|
if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){ |
||||
|
config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"}; |
||||
|
} |
||||
|
delete config[i].converters[j].timeseries[l].transformerType; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if(vm.extension.type == types.extensionType.mqtt) { |
||||
|
var brokers = vm.extension.configuration.brokers; |
||||
|
if(brokers && brokers.length > 0) { |
||||
|
for(let i=0;i<brokers.length;i++) { |
||||
|
if(brokers[i].mapping && brokers[i].mapping.length > 0) { |
||||
|
for(let j=0;j<brokers[i].mapping.length;j++) { |
||||
|
if(brokers[i].mapping[j].converterType == "json") { |
||||
|
delete brokers[i].mapping[j].converter.nameExp; |
||||
|
delete brokers[i].mapping[j].converter.typeExp; |
||||
|
} |
||||
|
delete brokers[i].mapping[j].converterType; |
||||
|
} |
||||
|
} |
||||
|
if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) { |
||||
|
for(let j=0;j<brokers[i].connectRequests.length;j++) { |
||||
|
delete brokers[i].connectRequests[j].nameExp; |
||||
|
} |
||||
|
} |
||||
|
if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) { |
||||
|
for(let j=0;j<brokers[i].disconnectRequests.length;j++) { |
||||
|
delete brokers[i].disconnectRequests[j].nameExp; |
||||
|
} |
||||
|
} |
||||
|
if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) { |
||||
|
for(let j=0;j<brokers[i].attributeRequests.length;j++) { |
||||
|
delete brokers[i].attributeRequests[j].nameExp; |
||||
|
} |
||||
|
for(let j=0;j<brokers[i].attributeRequests.length;j++) { |
||||
|
delete brokers[i].attributeRequests[j].attrKey; |
||||
|
} |
||||
|
for(let j=0;j<brokers[i].attributeRequests.length;j++) { |
||||
|
delete brokers[i].attributeRequests[j].requestId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function editTransformers(extension) { |
||||
|
if(extension.type == types.extensionType.http) { |
||||
|
var config = extension.configuration.converterConfigurations; |
||||
|
for(let i=0;i<config.length;i++) { |
||||
|
for(let j=0;j<config[i].converters.length;j++){ |
||||
|
for(let k=0;k<config[i].converters[j].attributes.length;k++){ |
||||
|
if(config[i].converters[j].attributes[k].transformer){ |
||||
|
if(config[i].converters[j].attributes[k].transformer.type == "intToDouble"){ |
||||
|
config[i].converters[j].attributes[k].transformerType = "toDouble"; |
||||
|
} else { |
||||
|
config[i].converters[j].attributes[k].transformerType = "custom"; |
||||
|
config[i].converters[j].attributes[k].transformer = js_beautify(config[i].converters[j].attributes[k].transformer, {indent_size: 4}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
for(let l=0;l<config[i].converters[j].timeseries.length;l++) { |
||||
|
if(config[i].converters[j].timeseries[l].transformer){ |
||||
|
if(config[i].converters[j].timeseries[l].transformer.type == "intToDouble"){ |
||||
|
config[i].converters[j].timeseries[l].transformerType = "toDouble"; |
||||
|
} else { |
||||
|
config[i].converters[j].timeseries[l].transformerType = "custom"; |
||||
|
config[i].converters[j].timeseries[l].transformer = js_beautify(config[i].converters[j].timeseries[l].transformer, {indent_size: 4}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if(extension.type == types.extensionType.mqtt) { |
||||
|
var brokers = extension.configuration.brokers; |
||||
|
for(let i=0;i<brokers.length;i++) { |
||||
|
if(brokers[i].mapping && brokers[i].mapping.length > 0) { |
||||
|
for(let j=0;j<brokers[i].mapping.length;j++) { |
||||
|
if(brokers[i].mapping[j].converter.type == "json") { |
||||
|
if(brokers[i].mapping[j].converter.deviceNameTopicExpression) { |
||||
|
brokers[i].mapping[j].converter.nameExp = "deviceNameTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].mapping[j].converter.nameExp = "deviceNameJsonExpression"; |
||||
|
} |
||||
|
if(brokers[i].mapping[j].converter.deviceTypeTopicExpression) { |
||||
|
brokers[i].mapping[j].converter.typeExp = "deviceTypeTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].mapping[j].converter.typeExp = "deviceTypeJsonExpression"; |
||||
|
} |
||||
|
brokers[i].mapping[j].converterType = "json"; |
||||
|
} else { |
||||
|
brokers[i].mapping[j].converterType = "custom"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) { |
||||
|
for(let j=0;j<brokers[i].connectRequests.length;j++) { |
||||
|
if(brokers[i].connectRequests[j].deviceNameTopicExpression) { |
||||
|
brokers[i].connectRequests[j].nameExp = "deviceNameTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].connectRequests[j].nameExp = "deviceNameJsonExpression"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) { |
||||
|
for(let j=0;j<brokers[i].disconnectRequests.length;j++) { |
||||
|
if(brokers[i].disconnectRequests[j].deviceNameTopicExpression) { |
||||
|
brokers[i].disconnectRequests[j].nameExp = "deviceNameTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].disconnectRequests[j].nameExp = "deviceNameJsonExpression"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) { |
||||
|
for(let j=0;j<brokers[i].attributeRequests.length;j++) { |
||||
|
if(brokers[i].attributeRequests[j].deviceNameTopicExpression) { |
||||
|
brokers[i].attributeRequests[j].nameExp = "deviceNameTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].attributeRequests[j].nameExp = "deviceNameJsonExpression"; |
||||
|
} |
||||
|
if(brokers[i].attributeRequests[j].attributeKeyTopicExpression) { |
||||
|
brokers[i].attributeRequests[j].attrKey = "attributeKeyTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].attributeRequests[j].attrKey = "attributeKeyJsonExpression"; |
||||
|
} |
||||
|
if(brokers[i].attributeRequests[j].requestIdTopicExpression) { |
||||
|
brokers[i].attributeRequests[j].requestId = "requestIdTopicExpression"; |
||||
|
} else { |
||||
|
brokers[i].attributeRequests[j].requestId = "requestIdJsonExpression"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export function ParseToNull() { |
||||
|
var linker = function (scope, elem, attrs, ngModel) { |
||||
|
ngModel.$parsers.push(function(value) { |
||||
|
if(value === "") { |
||||
|
return null; |
||||
|
} |
||||
|
return value; |
||||
|
}) |
||||
|
}; |
||||
|
return { |
||||
|
restrict: "A", |
||||
|
link: linker, |
||||
|
require: "ngModel" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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 class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}"> |
||||
|
<form name="theForm" ng-submit="vm.save()" novalidate> |
||||
|
<md-toolbar> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2> |
||||
|
<span flex></span> |
||||
|
<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="!loading" ng-show="loading"></md-progress-linear> |
||||
|
|
||||
|
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
||||
|
|
||||
|
<md-dialog-content> |
||||
|
<div class="md-dialog-content"> |
||||
|
<md-content class="md-padding" layout="column"> |
||||
|
<fieldset ng-disabled="loading"> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm.extensionId.$touched && theForm.extensionId.$invalid"> |
||||
|
<label translate>extension.extension-id</label> |
||||
|
<input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()"> |
||||
|
<div ng-messages="theForm.extensionId.$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
<div translate ng-message="uniqueIdValidation">extension.unique-id-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm.extensionType.$touched && theForm.extensionType.$invalid"> |
||||
|
<label translate>extension.extension-type</label> |
||||
|
|
||||
|
<md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type"> |
||||
|
<md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value"> |
||||
|
{{value}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
|
||||
|
<div ng-messages="theForm.extensionType.$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div> |
||||
|
<div tb-extension-form-mqtt config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.mqtt"></div> |
||||
|
<div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div> |
||||
|
</fieldset> |
||||
|
</md-content> |
||||
|
</div> |
||||
|
</md-dialog-content> |
||||
|
|
||||
|
<md-dialog-actions layout="row"> |
||||
|
<md-button type="submit" |
||||
|
class="md-raised md-primary" |
||||
|
> |
||||
|
{{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} |
||||
|
</md-button> |
||||
|
|
||||
|
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }} |
||||
|
</md-button> |
||||
|
</md-dialog-actions> |
||||
|
</form> |
||||
|
</md-dialog> |
||||
@ -0,0 +1,404 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 'angular-material-data-table/dist/md-data-table.min.css'; |
||||
|
import './extension-table.scss'; |
||||
|
|
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import extensionTableTemplate from './extension-table.tpl.html'; |
||||
|
import extensionDialogTemplate from './extension-dialog.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import ExtensionDialogController from './extension-dialog.controller' |
||||
|
import $ from 'jquery'; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ExtensionTableDirective() { |
||||
|
return { |
||||
|
restrict: "E", |
||||
|
scope: true, |
||||
|
bindToController: { |
||||
|
entityId: '=', |
||||
|
entityType: '@', |
||||
|
inWidget: '@?', |
||||
|
ctx: '=?', |
||||
|
entityName: '=' |
||||
|
}, |
||||
|
controller: ExtensionTableController, |
||||
|
controllerAs: 'vm', |
||||
|
templateUrl: extensionTableTemplate |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService, importExport) { |
||||
|
|
||||
|
let vm = this; |
||||
|
|
||||
|
vm.extensions = []; |
||||
|
vm.allExtensions = []; |
||||
|
vm.selectedExtensions = []; |
||||
|
vm.extensionsCount = 0; |
||||
|
|
||||
|
vm.query = { |
||||
|
order: 'id', |
||||
|
limit: 5, |
||||
|
page: 1, |
||||
|
search: null |
||||
|
}; |
||||
|
|
||||
|
vm.enterFilterMode = enterFilterMode; |
||||
|
vm.exitFilterMode = exitFilterMode; |
||||
|
vm.onReorder = onReorder; |
||||
|
vm.onPaginate = onPaginate; |
||||
|
vm.addExtension = addExtension; |
||||
|
vm.editExtension = editExtension; |
||||
|
vm.deleteExtension = deleteExtension; |
||||
|
vm.deleteExtensions = deleteExtensions; |
||||
|
vm.reloadExtensions = reloadExtensions; |
||||
|
vm.updateExtensions = updateExtensions; |
||||
|
|
||||
|
$scope.$watch("vm.entityId", function(newVal) { |
||||
|
if (newVal) { |
||||
|
if ($scope.subscriber) { |
||||
|
telemetryWebsocketService.unsubscribe($scope.subscriber); |
||||
|
$scope.subscriber = null; |
||||
|
} |
||||
|
|
||||
|
vm.subscribed = false; |
||||
|
vm.syncLastTime = $translate.instant('extension.sync.not-available'); |
||||
|
|
||||
|
subscribeForClientAttributes(); |
||||
|
|
||||
|
reloadExtensions(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$scope.$on('$destroy', function() { |
||||
|
if ($scope.subscriber) { |
||||
|
telemetryWebsocketService.unsubscribe($scope.subscriber); |
||||
|
$scope.subscriber = null; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$scope.$watch("vm.query.search", function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal) && vm.query.search != null) { |
||||
|
updateExtensions(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$scope.$watch('vm.selectedExtensions.length', function (newLength) { |
||||
|
var selectionMode = newLength ? true : false; |
||||
|
if (vm.ctx) { |
||||
|
if (selectionMode) { |
||||
|
vm.ctx.hideTitlePanel = true; |
||||
|
$scope.$emit("selectedExtensions", true); |
||||
|
} else if (vm.query.search == null) { |
||||
|
vm.ctx.hideTitlePanel = false; |
||||
|
$scope.$emit("selectedExtensions", false); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$scope.$on("showSearch", function($event, source) { |
||||
|
if(source.entityId == vm.entityId) { |
||||
|
enterFilterMode(); |
||||
|
$scope.$emit("filterMode", true); |
||||
|
} |
||||
|
}); |
||||
|
$scope.$on("refreshExtensions", function($event, source) { |
||||
|
if(source.entityId == vm.entityId) { |
||||
|
reloadExtensions(); |
||||
|
} |
||||
|
}); |
||||
|
$scope.$on("addExtension", function($event, source) { |
||||
|
if(source.entityId == vm.entityId) { |
||||
|
addExtension(); |
||||
|
} |
||||
|
}); |
||||
|
$scope.$on("exportExtensions", function($event, source) { |
||||
|
if(source.entityId == vm.entityId) { |
||||
|
vm.exportExtensions(source.entityName); |
||||
|
} |
||||
|
}); |
||||
|
$scope.$on("importExtensions", function($event, source) { |
||||
|
if(source.entityId == vm.entityId) { |
||||
|
vm.importExtensions(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function enterFilterMode() { |
||||
|
vm.query.search = ''; |
||||
|
if(vm.inWidget) { |
||||
|
vm.ctx.hideTitlePanel = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function exitFilterMode() { |
||||
|
vm.query.search = null; |
||||
|
updateExtensions(); |
||||
|
if(vm.inWidget) { |
||||
|
vm.ctx.hideTitlePanel = false; |
||||
|
$scope.$emit("filterMode", false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onReorder() { |
||||
|
updateExtensions(); |
||||
|
} |
||||
|
|
||||
|
function onPaginate() { |
||||
|
updateExtensions(); |
||||
|
} |
||||
|
|
||||
|
function addExtension($event) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
openExtensionDialog($event); |
||||
|
} |
||||
|
|
||||
|
function editExtension($event, extension) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
openExtensionDialog($event, extension); |
||||
|
} |
||||
|
|
||||
|
function openExtensionDialog($event, extension) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
var isAdd = false; |
||||
|
if(!extension) { |
||||
|
isAdd = true; |
||||
|
} |
||||
|
$mdDialog.show({ |
||||
|
controller: ExtensionDialogController, |
||||
|
controllerAs: 'vm', |
||||
|
templateUrl: extensionDialogTemplate, |
||||
|
parent: angular.element($document[0].body), |
||||
|
locals: { |
||||
|
isAdd: isAdd, |
||||
|
allExtensions: vm.allExtensions, |
||||
|
entityId: vm.entityId, |
||||
|
entityType: vm.entityType, |
||||
|
extension: extension |
||||
|
}, |
||||
|
bindToController: true, |
||||
|
targetEvent: $event, |
||||
|
fullscreen: true, |
||||
|
skipHide: true |
||||
|
}).then(function() { |
||||
|
reloadExtensions(); |
||||
|
}, function () { |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function deleteExtension($event, extension) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
if(extension) { |
||||
|
var title = $translate.instant('extension.delete-extension-title', {extensionId: extension.id}); |
||||
|
var content = $translate.instant('extension.delete-extension-text'); |
||||
|
|
||||
|
var confirm = $mdDialog.confirm() |
||||
|
.targetEvent($event) |
||||
|
.title(title) |
||||
|
.htmlContent(content) |
||||
|
.ariaLabel(title) |
||||
|
.cancel($translate.instant('action.no')) |
||||
|
.ok($translate.instant('action.yes')); |
||||
|
$mdDialog.show(confirm).then(function() { |
||||
|
var editedExtensions = vm.allExtensions.filter(function(ext) { |
||||
|
return ext.id !== extension.id; |
||||
|
}); |
||||
|
var editedValue = angular.toJson(editedExtensions); |
||||
|
attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( |
||||
|
function success() { |
||||
|
reloadExtensions(); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function deleteExtensions($event) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
if (vm.selectedExtensions && vm.selectedExtensions.length > 0) { |
||||
|
var title = $translate.instant('extension.delete-extensions-title', {count: vm.selectedExtensions.length}, 'messageformat'); |
||||
|
var content = $translate.instant('extension.delete-extensions-text'); |
||||
|
|
||||
|
var confirm = $mdDialog.confirm() |
||||
|
.targetEvent($event) |
||||
|
.title(title) |
||||
|
.htmlContent(content) |
||||
|
.ariaLabel(title) |
||||
|
.cancel($translate.instant('action.no')) |
||||
|
.ok($translate.instant('action.yes')); |
||||
|
$mdDialog.show(confirm).then(function () { |
||||
|
var editedExtensions = angular.copy(vm.allExtensions); |
||||
|
for (var i = 0; i < vm.selectedExtensions.length; i++) { |
||||
|
editedExtensions = editedExtensions.filter(function (ext) { |
||||
|
return ext.id !== vm.selectedExtensions[i].id; |
||||
|
}); |
||||
|
} |
||||
|
var editedValue = angular.toJson(editedExtensions); |
||||
|
attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then( |
||||
|
function success() { |
||||
|
reloadExtensions(); |
||||
|
} |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function reloadExtensions() { |
||||
|
vm.subscribed = false; |
||||
|
vm.allExtensions.length = 0; |
||||
|
vm.extensions.length = 0; |
||||
|
vm.extensionsPromise = attributeService.getEntityAttributesValues(vm.entityType, vm.entityId, types.attributesScope.shared.value, ["configuration"]); |
||||
|
vm.extensionsPromise.then( |
||||
|
function success(data) { |
||||
|
if (data.length) { |
||||
|
vm.allExtensions = angular.fromJson(data[0].value); |
||||
|
} else { |
||||
|
vm.allExtensions = []; |
||||
|
} |
||||
|
|
||||
|
vm.selectedExtensions = []; |
||||
|
updateExtensions(); |
||||
|
vm.extensionsPromise = null; |
||||
|
}, |
||||
|
function fail() { |
||||
|
vm.extensions = []; |
||||
|
vm.selectedExtensions = []; |
||||
|
updateExtensions(); |
||||
|
vm.extensionsPromise = null; |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
function updateExtensions() { |
||||
|
vm.selectedExtensions = []; |
||||
|
var result = $filter('orderBy')(vm.allExtensions, vm.query.order); |
||||
|
if (vm.query.search != null) { |
||||
|
result = $filter('filter')(result, function(extension) { |
||||
|
if(!vm.query.search || (extension.id.indexOf(vm.query.search) != -1) || (extension.type.indexOf(vm.query.search) != -1)) { |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
}); |
||||
|
} |
||||
|
vm.extensionsCount = result.length; |
||||
|
var startIndex = vm.query.limit * (vm.query.page - 1); |
||||
|
vm.extensions = result.slice(startIndex, startIndex + vm.query.limit); |
||||
|
|
||||
|
vm.extensionsJSON = angular.toJson(vm.extensions); |
||||
|
checkForSync(); |
||||
|
} |
||||
|
|
||||
|
function subscribeForClientAttributes() { |
||||
|
if (!vm.subscribed) { |
||||
|
if (vm.entityId && vm.entityType) { |
||||
|
$scope.subscriber = { |
||||
|
subscriptionCommands: [{ |
||||
|
entityType: vm.entityType, |
||||
|
entityId: vm.entityId, |
||||
|
scope: 'CLIENT_SCOPE' |
||||
|
}], |
||||
|
type: 'attribute', |
||||
|
onData: function (data) { |
||||
|
if (data.data) { |
||||
|
onSubscriptionData(data.data); |
||||
|
} |
||||
|
vm.subscribed = true; |
||||
|
} |
||||
|
}; |
||||
|
telemetryWebsocketService.subscribe($scope.subscriber); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
function onSubscriptionData(data) { |
||||
|
|
||||
|
if ($.isEmptyObject(data)) { |
||||
|
vm.appliedConfiguration = undefined; |
||||
|
} else { |
||||
|
if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) { |
||||
|
vm.appliedConfiguration = data.appliedConfiguration[0][1]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateExtensions(); |
||||
|
$scope.$digest(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function checkForSync() { |
||||
|
if (vm.appliedConfiguration && vm.extensionsJSON && vm.appliedConfiguration === vm.extensionsJSON) { |
||||
|
vm.syncStatus = $translate.instant('extension.sync.sync'); |
||||
|
vm.syncLastTime = formatDate(); |
||||
|
$scope.isSync = true; |
||||
|
} else { |
||||
|
vm.syncStatus = $translate.instant('extension.sync.not-sync'); |
||||
|
|
||||
|
$scope.isSync = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function formatDate(date) { |
||||
|
let d; |
||||
|
if (date) { |
||||
|
d = date; |
||||
|
} else { |
||||
|
d = new Date(); |
||||
|
} |
||||
|
|
||||
|
d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds()); |
||||
|
return d; |
||||
|
|
||||
|
function addZero(num) { |
||||
|
if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) { |
||||
|
num = '0' + num; |
||||
|
} |
||||
|
return num; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
vm.importExtensions = function($event) { |
||||
|
importExport.importExtension($event, {"entityType":vm.entityType, "entityId":vm.entityId, "successFunc":reloadExtensions}); |
||||
|
}; |
||||
|
vm.exportExtensions = function(widgetSourceEntityName) { |
||||
|
if(vm.inWidget) { |
||||
|
importExport.exportToPc(vm.extensionsJSON, widgetSourceEntityName + '_configuration.json'); |
||||
|
} else { |
||||
|
importExport.exportToPc(vm.extensionsJSON, vm.entityName + '_configuration.json'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/*change function for widget implementing, like vm.exportExtensions*/ |
||||
|
vm.exportExtension = function($event, extension) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
importExport.exportToPc(extension, vm.entityName +'_'+ extension.id +'_configuration.json'); |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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 '../../scss/constants'; |
||||
|
|
||||
|
|
||||
|
.extension-table { |
||||
|
|
||||
|
md-input-container .md-errors-spacer { |
||||
|
min-height: 0; |
||||
|
} |
||||
|
|
||||
|
/*&.tb-data-table table.md-table tbody tr td.tb-action-cell, |
||||
|
&.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell { |
||||
|
width: 114px; |
||||
|
}*/ |
||||
|
.sync-widget { |
||||
|
max-height: 90px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.toolbar-widget { |
||||
|
min-height: 39px; |
||||
|
max-height: 39px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.extension__syncStatus--black { |
||||
|
color: #000000!important; |
||||
|
} |
||||
|
.extension__syncStatus--green { |
||||
|
color: #228634!important; |
||||
|
} |
||||
|
.extension__syncStatus--red { |
||||
|
color: #862222!important; |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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-content flex class="md-padding tb-absolute-fill tb-data-table extension-table" layout="column"> |
||||
|
<div layout="column" class="md-whiteframe-z1" ng-class="{'tb-absolute-fill' : vm.inWidget}"> |
||||
|
<md-toolbar ng-if="!vm.inWidget" class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length |
||||
|
&& vm.query.search === null"> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<span translate>{{ 'extension.extensions' }}</span> |
||||
|
<span flex></span> |
||||
|
|
||||
|
<md-button class="md-icon-button" ng-click="vm.importExtensions($event)"> |
||||
|
<md-icon>file_upload</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'extension.import-extensions-configuration' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button class="md-icon-button" ng-click="vm.exportExtensions()"> |
||||
|
<md-icon>file_download</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'extension.export-extensions-configuration' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button class="md-icon-button" ng-click="vm.addExtension($event)"> |
||||
|
<md-icon>add</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.add' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()"> |
||||
|
<md-icon>search</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.search' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button class="md-icon-button" ng-click="vm.reloadExtensions()"> |
||||
|
<md-icon>refresh</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.refresh' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length |
||||
|
&& vm.query.search != null" ng-class="{'toolbar-widget' : vm.inWidget}"> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> |
||||
|
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.search' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-input-container flex> |
||||
|
<label> </label> |
||||
|
<input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/> |
||||
|
</md-input-container> |
||||
|
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()"> |
||||
|
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon> |
||||
|
<md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}"> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-toolbar class="md-table-toolbar alternate" ng-show="vm.selectedExtensions.length" ng-class="{'toolbar-widget' : vm.inWidget}"> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<span translate |
||||
|
translate-values="{count: vm.selectedExtensions.length}" |
||||
|
translate-interpolation="messageformat">extension.selected-extensions</span> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.deleteExtensions($event)"> |
||||
|
<md-icon>delete</md-icon> |
||||
|
<md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}"> |
||||
|
{{ 'action.delete' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
|
||||
|
<div class="md-padding" flex layout="row" ng-class="{'sync-widget' : vm.inWidget}"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.sync.status</label> |
||||
|
<input ng-model="vm.syncStatus" |
||||
|
ng-class="{'extension__syncStatus--green':isSync, 'extension__syncStatus--red':!isSync}" |
||||
|
disabled |
||||
|
> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.sync.last-sync-time</label> |
||||
|
<input ng-model="vm.syncLastTime" |
||||
|
class="extension__syncStatus--black" |
||||
|
disabled |
||||
|
> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
<md-table-container flex> |
||||
|
<table md-table md-row-select multiple="" ng-model="vm.selectedExtensions" md-progress="vm.extensionsDeferred.promise"> |
||||
|
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> |
||||
|
<tr md-row> |
||||
|
<th md-column md-order-by="id"><span translate>extension.id</span></th> |
||||
|
<th md-column md-order-by="type"><span translate>extension.type</span></th> |
||||
|
<th md-column><span> </span></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody md-body> |
||||
|
<tr md-row md-select="extension" md-select-id="extension" md-auto-select ng-repeat="extension in vm.extensions"> |
||||
|
<td md-cell>{{ extension.id }}</td> |
||||
|
<td md-cell>{{ extension.type }}</td> |
||||
|
<td md-cell class="tb-action-cell"> |
||||
|
|
||||
|
<!--<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.exportExtension($event, extension)"> |
||||
|
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">file_download</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'extension.export-extension' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button>--> |
||||
|
|
||||
|
<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)"> |
||||
|
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'extension.edit' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button class="md-icon-button" aria-label="{{ 'action.delete' | translate }}" ng-click="vm.deleteExtension($event, extension)"> |
||||
|
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">delete</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'extension.delete' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<md-divider ng-if="vm.inWidget"></md-divider> |
||||
|
</md-table-container> |
||||
|
<md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]" |
||||
|
md-page="vm.query.page" md-total="{{vm.extensionsCount}}" |
||||
|
md-on-paginate="vm.onPaginate" md-page-select> |
||||
|
</md-table-pagination> |
||||
|
</div> |
||||
|
|
||||
|
</md-content> |
||||
@ -0,0 +1,147 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 'brace/ext/language_tools'; |
||||
|
import 'brace/mode/json'; |
||||
|
import 'brace/theme/github'; |
||||
|
|
||||
|
import './extension-form.scss'; |
||||
|
|
||||
|
/* eslint-disable angular/log */ |
||||
|
|
||||
|
import extensionFormHttpTemplate from './extension-form-http.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) { |
||||
|
|
||||
|
var linker = function(scope, element) { |
||||
|
|
||||
|
var template = $templateCache.get(extensionFormHttpTemplate); |
||||
|
element.html(template); |
||||
|
|
||||
|
scope.types = types; |
||||
|
scope.theForm = scope.$parent.theForm; |
||||
|
|
||||
|
scope.extensionCustomTransformerOptions = { |
||||
|
useWrapMode: false, |
||||
|
mode: 'json', |
||||
|
showGutter: true, |
||||
|
showPrintMargin: true, |
||||
|
theme: 'github', |
||||
|
advanced: { |
||||
|
enableSnippets: true, |
||||
|
enableBasicAutocompletion: true, |
||||
|
enableLiveAutocompletion: true |
||||
|
}, |
||||
|
onLoad: function(_ace) { |
||||
|
_ace.$blockScrolling = 1; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
scope.addConverterConfig = function() { |
||||
|
var newConverterConfig = {converterId:"", converters:[]}; |
||||
|
scope.converterConfigs.push(newConverterConfig); |
||||
|
|
||||
|
scope.converterConfigs[scope.converterConfigs.length - 1].converters = []; |
||||
|
scope.addConverter(scope.converterConfigs[scope.converterConfigs.length - 1].converters); |
||||
|
}; |
||||
|
|
||||
|
scope.removeConverterConfig = function(config) { |
||||
|
var index = scope.converterConfigs.indexOf(config); |
||||
|
if (index > -1) { |
||||
|
scope.converterConfigs.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.addConverter = function(converters) { |
||||
|
var newConverter = { |
||||
|
deviceNameJsonExpression:"", |
||||
|
deviceTypeJsonExpression:"", |
||||
|
attributes:[], |
||||
|
timeseries:[] |
||||
|
}; |
||||
|
converters.push(newConverter); |
||||
|
}; |
||||
|
|
||||
|
scope.removeConverter = function(converter, converters) { |
||||
|
var index = converters.indexOf(converter); |
||||
|
if (index > -1) { |
||||
|
converters.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.addAttribute = function(attributes) { |
||||
|
var newAttribute = {type:"", key:"", value:""}; |
||||
|
attributes.push(newAttribute); |
||||
|
}; |
||||
|
|
||||
|
scope.removeAttribute = function(attribute, attributes) { |
||||
|
var index = attributes.indexOf(attribute); |
||||
|
if (index > -1) { |
||||
|
attributes.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
if(scope.isAdd) { |
||||
|
scope.converterConfigs = scope.config.converterConfigurations; |
||||
|
scope.addConverterConfig(); |
||||
|
} else { |
||||
|
scope.converterConfigs = scope.config.converterConfigurations; |
||||
|
} |
||||
|
|
||||
|
scope.transformerTypeChange = function(attribute) { |
||||
|
attribute.transformer = ""; |
||||
|
}; |
||||
|
|
||||
|
scope.validateTransformer = function (model, editorName) { |
||||
|
if(model && model.length) { |
||||
|
try { |
||||
|
angular.fromJson(model); |
||||
|
scope.theForm[editorName].$setValidity('transformerJSON', true); |
||||
|
} catch(e) { |
||||
|
scope.theForm[editorName].$setValidity('transformerJSON', false); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.collapseValidation = function(index, id) { |
||||
|
var invalidState = angular.element('#'+id+':has(.ng-invalid)'); |
||||
|
if(invalidState.length) { |
||||
|
invalidState.addClass('inner-invalid'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.expandValidation = function (index, id) { |
||||
|
var invalidState = angular.element('#'+id); |
||||
|
invalidState.removeClass('inner-invalid'); |
||||
|
}; |
||||
|
|
||||
|
$compile(element.contents())(scope); |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
restrict: "A", |
||||
|
link: linker, |
||||
|
scope: { |
||||
|
config: "=", |
||||
|
isAdd: "=" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,311 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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-card class="extension-form extension-http"> |
||||
|
<md-card-title> |
||||
|
<md-card-title-text> |
||||
|
<span translate class="md-headline">extension.configuration</span> |
||||
|
</md-card-title-text> |
||||
|
</md-card-title> |
||||
|
<md-card-content> |
||||
|
<v-accordion id="http-converter-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="http-converters-pane" expanded="true"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.converter-configurations' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="converterConfigs.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" |
||||
|
class="md-icon-button" |
||||
|
ng-click="removeConverterConfig(config)" |
||||
|
ng-hide="converterConfigs.length < 2" |
||||
|
> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
|
||||
|
<md-input-container class="md-block" md-is-error="theForm['httpConverterId_' + configIndex].$touched && theForm['httpConverterId_' + configIndex].$invalid"> |
||||
|
<label translate>extension.converter-id</label> |
||||
|
<input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId"> |
||||
|
<div ng-messages="theForm['httpConverterId_' + configIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.token</label> |
||||
|
<input name="httpToken" ng-model="config.token" parse-to-null> |
||||
|
</md-input-container> |
||||
|
<v-accordion id="http-converters-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="http-converters-pane_{{configIndex}}" expanded="true"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.converters' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="config.converters.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" |
||||
|
ng-repeat="(converterIndex,converter) in config.converters" |
||||
|
> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" |
||||
|
class="md-icon-button" |
||||
|
ng-click="removeConverter(converter, config.converters)" |
||||
|
ng-hide="config.converters.length < 2" |
||||
|
> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<md-input-container class="md-block" md-is-error="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceNameExp_' + configIndex + converterIndex].$invalid"> |
||||
|
<label translate>extension.device-name-expression</label> |
||||
|
<input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression"> |
||||
|
<div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block" md-is-error="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$invalid"> |
||||
|
<label translate>extension.device-type-expression</label> |
||||
|
<input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression"> |
||||
|
<div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<v-accordion id="http-attributes-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="http-attributes-pane_{{configIndex}}{{converterIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.attributes' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="converter.attributes.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(attributeIndex, attribute) in converter.attributes"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, converter.attributes)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$invalid"> |
||||
|
<label translate>extension.key</label> |
||||
|
<input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key"> |
||||
|
<div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$invalid"> |
||||
|
<label translate>extension.type</label> |
||||
|
<md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type"> |
||||
|
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> |
||||
|
{{attrTypeValue | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$invalid"> |
||||
|
<label translate>extension.value</label> |
||||
|
<input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value"> |
||||
|
<div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
|
||||
|
<md-input-container flex="40" class="md-block"> |
||||
|
<label translate>extension.transformer</label> |
||||
|
<md-select name="httpAttributeTransformer" ng-model="attribute.transformerType" ng-change="transformerTypeChange(attribute)"> |
||||
|
<md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType"> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<div ng-if='attribute.transformerType == "custom"'> |
||||
|
<div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div> |
||||
|
<div flex class="tb-extension-custom-transformer-panel"> |
||||
|
<div flex class="tb-extension-custom-transformer" |
||||
|
ui-ace="extensionCustomTransformerOptions" |
||||
|
ng-model="attribute.transformer" |
||||
|
name="attributeCustomTransformer_{{configIndex}}{{converterIndex}}{{attributeIndex}}" |
||||
|
ng-change='validateTransformer(attribute.transformer,"attributeCustomTransformer_" + configIndex + converterIndex + attributeIndex)' |
||||
|
required> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-error-messages" ng-messages="theForm['attributeCustomTransformer_' + configIndex + converterIndex + attributeIndex].$error" role="alert"> |
||||
|
<div ng-message="required" class="tb-error-message" translate>extension.json-required</div> |
||||
|
<div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-attribute</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
|
||||
|
<v-accordion id="http-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="http-timeseries-pane_{{configIndex}}{{converterIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.timeseries' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="converter.timeseries.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in converter.timeseries"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, converter.timeseries)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$invalid"> |
||||
|
<label translate>extension.key</label> |
||||
|
<input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key"> |
||||
|
<div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$invalid"> |
||||
|
<label translate>extension.type</label> |
||||
|
<md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type"> |
||||
|
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> |
||||
|
{{attrTypeValue | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$invalid"> |
||||
|
<label translate>extension.value</label> |
||||
|
<input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> |
||||
|
<div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
|
||||
|
<md-input-container flex="40" class="md-block"> |
||||
|
<label translate>extension.transformer</label> |
||||
|
<md-select name="httpTimeseriesTransformer" ng-model="timeseries.transformerType" ng-change="transformerTypeChange(timeseries)"> |
||||
|
<md-option ng-repeat="(transformerType, value) in types.extensionTransformerType" ng-value="transformerType"> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<div ng-if='timeseries.transformerType == "custom"'> |
||||
|
<div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div> |
||||
|
<div flex class="tb-extension-custom-transformer-panel"> |
||||
|
<div flex class="tb-extension-custom-transformer" |
||||
|
ui-ace="extensionCustomTransformerOptions" |
||||
|
ng-model="timeseries.transformer" |
||||
|
name="timeseriesCustomTransformer_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" |
||||
|
ng-change='validateTransformer(timeseries.transformer,"timeseriesCustomTransformer_" + configIndex + converterIndex + timeseriesIndex)' |
||||
|
required> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-error-messages" ng-messages="theForm['timeseriesCustomTransformer_' + configIndex + converterIndex + timeseriesIndex].$error" role="alert"> |
||||
|
<div ng-message="required" class="tb-error-message" translate>extension.json-required</div> |
||||
|
<div ng-message="transformerJSON" class="tb-error-message" translate>extension.json-parse</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-timeseries</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-converter</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-config</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
<!--{{config}}--> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
@ -0,0 +1,347 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 './extension-form.scss'; |
||||
|
|
||||
|
/* eslint-disable angular/log */ |
||||
|
|
||||
|
import extensionFormMqttTemplate from './extension-form-mqtt.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) { |
||||
|
|
||||
|
var linker = function(scope, element) { |
||||
|
|
||||
|
var template = $templateCache.get(extensionFormMqttTemplate); |
||||
|
element.html(template); |
||||
|
|
||||
|
scope.types = types; |
||||
|
scope.theForm = scope.$parent.theForm; |
||||
|
|
||||
|
scope.deviceNameExpressions = { |
||||
|
deviceNameJsonExpression: "extension.converter-json", |
||||
|
deviceNameTopicExpression: "extension.topic" |
||||
|
}; |
||||
|
scope.deviceTypeExpressions = { |
||||
|
deviceTypeJsonExpression: "extension.converter-json", |
||||
|
deviceTypeTopicExpression: "extension.topic" |
||||
|
}; |
||||
|
scope.attributeKeyExpressions = { |
||||
|
attributeKeyJsonExpression: "extension.converter-json", |
||||
|
attributeKeyTopicExpression: "extension.topic" |
||||
|
}; |
||||
|
scope.requestIdExpressions = { |
||||
|
requestIdJsonExpression: "extension.converter-json", |
||||
|
requestIdTopicExpression: "extension.topic" |
||||
|
} |
||||
|
|
||||
|
scope.extensionCustomConverterOptions = { |
||||
|
useWrapMode: false, |
||||
|
mode: 'json', |
||||
|
showGutter: true, |
||||
|
showPrintMargin: true, |
||||
|
theme: 'github', |
||||
|
advanced: { |
||||
|
enableSnippets: true, |
||||
|
enableBasicAutocompletion: true, |
||||
|
enableLiveAutocompletion: true |
||||
|
}, |
||||
|
onLoad: function(_ace) { |
||||
|
_ace.$blockScrolling = 1; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.updateValidity = function () { |
||||
|
if(scope.brokers.length) { |
||||
|
for(let i=0;i<scope.brokers.length;i++) { |
||||
|
if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) { |
||||
|
if(!(scope.brokers[i].credentials.caCert && scope.brokers[i].credentials.privateKey && scope.brokers[i].credentials.cert)) { |
||||
|
scope.theForm.$setValidity('cert.PEM', false); |
||||
|
break; |
||||
|
} else { |
||||
|
scope.theForm.$setValidity('cert.PEM', true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.$watch('brokers', function() { |
||||
|
scope.updateValidity(); |
||||
|
}, true); |
||||
|
|
||||
|
scope.addBroker = function() { |
||||
|
var newBroker = { |
||||
|
host: "localhost", |
||||
|
port: 1882, |
||||
|
ssl: false, |
||||
|
retryInterval: 3000, |
||||
|
credentials: {type:"anonymous"}, |
||||
|
mapping: [], |
||||
|
connectRequests: [], |
||||
|
disconnectRequests: [], |
||||
|
attributeRequests: [], |
||||
|
attributeUpdates: [], |
||||
|
serverSideRpc: [] |
||||
|
}; |
||||
|
scope.brokers.push(newBroker); |
||||
|
}; |
||||
|
|
||||
|
scope.removeBroker = function(broker) { |
||||
|
var index = scope.brokers.indexOf(broker); |
||||
|
if (index > -1) { |
||||
|
scope.brokers.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if(scope.isAdd) { |
||||
|
scope.brokers = []; |
||||
|
scope.config.brokers = scope.brokers; |
||||
|
scope.addBroker(); |
||||
|
} else { |
||||
|
scope.brokers = scope.config.brokers; |
||||
|
} |
||||
|
|
||||
|
scope.addMap = function(mapping) { |
||||
|
var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}}; |
||||
|
|
||||
|
mapping.push(newMap); |
||||
|
}; |
||||
|
|
||||
|
scope.removeMap = function(map, mapping) { |
||||
|
var index = mapping.indexOf(map); |
||||
|
if (index > -1) { |
||||
|
mapping.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.addAttribute = function(attributes) { |
||||
|
var newAttribute = {type:"", key:"", value:""}; |
||||
|
attributes.push(newAttribute); |
||||
|
}; |
||||
|
|
||||
|
scope.removeAttribute = function(attribute, attributes) { |
||||
|
var index = attributes.indexOf(attribute); |
||||
|
if (index > -1) { |
||||
|
attributes.splice(index, 1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.addConnectRequest = function(requests, type) { |
||||
|
var newRequest = {}; |
||||
|
if(type == "connect") { |
||||
|
newRequest.topicFilter = "sensors/connect"; |
||||
|
} else { |
||||
|
newRequest.topicFilter = "sensors/disconnect"; |
||||
|
} |
||||
|
requests.push(newRequest); |
||||
|
}; |
||||
|
|
||||
|
scope.addAttributeRequest = function(requests) { |
||||
|
var newRequest = { |
||||
|
topicFilter: "sensors/attributes", |
||||
|
clientScope: false, |
||||
|
responseTopicExpression: "sensors/${deviceName}/attributes/${responseId}", |
||||
|
valueExpression: "${attributeValue}" |
||||
|
}; |
||||
|
requests.push(newRequest); |
||||
|
}; |
||||
|
|
||||
|
scope.addAttributeUpdate = function(updates) { |
||||
|
var newUpdate = { |
||||
|
deviceNameFilter: ".*", |
||||
|
attributeFilter: ".*", |
||||
|
topicExpression: "sensor/${deviceName}/${attributeKey}", |
||||
|
valueExpression: "{\"${attributeKey}\":\"${attributeValue}\"}" |
||||
|
} |
||||
|
updates.push(newUpdate); |
||||
|
}; |
||||
|
|
||||
|
scope.addServerSideRpc = function(rpcRequests) { |
||||
|
var newRpc = { |
||||
|
deviceNameFilter: ".*", |
||||
|
methodFilter: "echo", |
||||
|
requestTopicExpression: "sensor/${deviceName}/request/${methodName}/${requestId}", |
||||
|
responseTopicExpression: "sensor/${deviceName}/response/${methodName}/${requestId}", |
||||
|
responseTimeout: 10000, |
||||
|
valueExpression: "${params}" |
||||
|
}; |
||||
|
rpcRequests.push(newRpc); |
||||
|
}; |
||||
|
|
||||
|
scope.changeCredentials = function(broker) { |
||||
|
var type = broker.credentials.type; |
||||
|
broker.credentials = {}; |
||||
|
broker.credentials.type = type; |
||||
|
}; |
||||
|
|
||||
|
scope.changeConverterType = function(map) { |
||||
|
if(map.converterType == "custom"){ |
||||
|
map.converter = ""; |
||||
|
} |
||||
|
if(map.converterType == "json") { |
||||
|
map.converter = {attributes:[],timeseries:[]}; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.changeNameExpression = function(element, type) { |
||||
|
if(element.nameExp == "deviceNameJsonExpression") { |
||||
|
if(element.deviceNameTopicExpression) { |
||||
|
delete element.deviceNameTopicExpression; |
||||
|
} |
||||
|
if(type) { |
||||
|
element.deviceNameJsonExpression = "${$.serialNumber}"; |
||||
|
} |
||||
|
} |
||||
|
if(element.nameExp == "deviceNameTopicExpression") { |
||||
|
if(element.deviceNameJsonExpression) { |
||||
|
delete element.deviceNameJsonExpression; |
||||
|
} |
||||
|
if(type && type == "connect") { |
||||
|
element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/connect)"; |
||||
|
} |
||||
|
if(type && type == "disconnect") { |
||||
|
element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/disconnect)"; |
||||
|
} |
||||
|
if(type && type == "attribute") { |
||||
|
element.deviceNameTopicExpression = "(?<=sensors\\/)(.*?)(?=\\/attributes)"; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.changeTypeExpression = function(converter) { |
||||
|
if(converter.typeExp == "deviceTypeJsonExpression") { |
||||
|
if(converter.deviceTypeTopicExpression) { |
||||
|
delete converter.deviceTypeTopicExpression; |
||||
|
} |
||||
|
} |
||||
|
if(converter.typeExp == "deviceTypeTopicExpression") { |
||||
|
if(converter.deviceTypeJsonExpression) { |
||||
|
delete converter.deviceTypeJsonExpression; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.changeAttrKeyExpression = function(request) { |
||||
|
if(request.attrKey == "attributeKeyJsonExpression") { |
||||
|
if(request.attributeKeyTopicExpression) { |
||||
|
delete request.attributeKeyTopicExpression; |
||||
|
} |
||||
|
request.attributeKeyJsonExpression = "${$.key}"; |
||||
|
} |
||||
|
if(request.attrKey == "attributeKeyTopicExpression") { |
||||
|
if(request.attributeKeyJsonExpression) { |
||||
|
delete request.attributeKeyJsonExpression; |
||||
|
} |
||||
|
request.attributeKeyTopicExpression = "(?<=attributes\\/)(.*?)(?=\\/request)"; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.changeRequestIdExpression = function(request) { |
||||
|
if(request.requestId == "requestIdJsonExpression") { |
||||
|
if(request.requestIdTopicExpression) { |
||||
|
delete request.requestIdTopicExpression; |
||||
|
} |
||||
|
request.requestIdJsonExpression = "${$.requestId}"; |
||||
|
} |
||||
|
if(request.requestId == "requestIdTopicExpression") { |
||||
|
if(request.requestIdJsonExpression) { |
||||
|
delete request.requestIdJsonExpression; |
||||
|
} |
||||
|
request.requestIdTopicExpression = "(?<=request\\/)(.*?)($)"; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.validateCustomConverter = function(model, editorName) { |
||||
|
if(model && model.length) { |
||||
|
try { |
||||
|
angular.fromJson(model); |
||||
|
scope.theForm[editorName].$setValidity('converterJSON', true); |
||||
|
} catch(e) { |
||||
|
scope.theForm[editorName].$setValidity('converterJSON', false); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.fileAdded = function($file, broker, fileType) { |
||||
|
var reader = new FileReader(); |
||||
|
reader.onload = function(event) { |
||||
|
scope.$apply(function() { |
||||
|
if(event.target.result) { |
||||
|
scope.theForm.$setDirty(); |
||||
|
var addedFile = event.target.result; |
||||
|
if (addedFile && addedFile.length > 0) { |
||||
|
if(fileType == "caCert") { |
||||
|
broker.credentials.caCertFileName = $file.name; |
||||
|
broker.credentials.caCert = addedFile.replace(/^data.*base64,/, ""); |
||||
|
} |
||||
|
if(fileType == "privateKey") { |
||||
|
broker.credentials.privateKeyFileName = $file.name; |
||||
|
broker.credentials.privateKey = addedFile.replace(/^data.*base64,/, ""); |
||||
|
} |
||||
|
if(fileType == "Cert") { |
||||
|
broker.credentials.certFileName = $file.name; |
||||
|
broker.credentials.cert = addedFile.replace(/^data.*base64,/, ""); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
reader.readAsDataURL($file.file); |
||||
|
}; |
||||
|
|
||||
|
scope.clearFile = function(broker, fileType) { |
||||
|
scope.theForm.$setDirty(); |
||||
|
if(fileType == "caCert") { |
||||
|
broker.credentials.caCertFileName = null; |
||||
|
broker.credentials.caCert = null; |
||||
|
} |
||||
|
if(fileType == "privateKey") { |
||||
|
broker.credentials.privateKeyFileName = null; |
||||
|
broker.credentials.privateKey = null; |
||||
|
} |
||||
|
if(fileType == "Cert") { |
||||
|
broker.credentials.certFileName = null; |
||||
|
broker.credentials.cert = null; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.collapseValidation = function(index, id) { |
||||
|
var invalidState = angular.element('#'+id+':has(.ng-invalid)'); |
||||
|
if(invalidState.length) { |
||||
|
invalidState.addClass('inner-invalid'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.expandValidation = function (index, id) { |
||||
|
var invalidState = angular.element('#'+id); |
||||
|
invalidState.removeClass('inner-invalid'); |
||||
|
}; |
||||
|
|
||||
|
$compile(element.contents())(scope); |
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
restrict: "A", |
||||
|
link: linker, |
||||
|
scope: { |
||||
|
config: "=", |
||||
|
isAdd: "=" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,860 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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-card class="extension-form extension-mqtt"> |
||||
|
<md-card-title name="testValid"> |
||||
|
<md-card-title-text> |
||||
|
<span translate class="md-headline">extension.configuration</span> |
||||
|
</md-card-title-text> |
||||
|
</md-card-title> |
||||
|
<md-card-content> |
||||
|
<v-accordion id="mqtt-brokers-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-brokers-pane" expanded="true"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.brokers' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="brokers.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(brokerIndex,broker) in brokers"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeBroker(broker)" ng-hide="brokers.length < 2"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block"> |
||||
|
<label translate>extension.port</label> |
||||
|
<input required type="number" min="1" max="65535" name="mqttPort_{{brokerIndex}}" ng-model="broker.port"> |
||||
|
<div ng-messages="theForm['mqttPort_' + brokerIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
<div translate ng-message="min">extension.port-range</div> |
||||
|
<div translate ng-message="max">extension.port-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="60" class="md-block"> |
||||
|
<label translate>extension.host</label> |
||||
|
<input required name="mqttHost_{{brokerIndex}}" ng-model="broker.host"> |
||||
|
<div ng-messages="theForm['mqttHost_' + brokerIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block"> |
||||
|
<label translate>extension.retry-interval</label> |
||||
|
<input required type="number" name="mqttRetryInterval_{{brokerIndex}}" ng-model="broker.retryInterval"> |
||||
|
<div ng-messages="theForm['mqttRetryInterval_' + brokerIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.credentials</label> |
||||
|
<md-select required name="mqttCredentials_{{brokerIndex}}" ng-model="broker.credentials.type" ng-change="changeCredentials(broker)"> |
||||
|
<md-option ng-repeat="(credentialsType, credentialsValue) in types.mqttCredentialTypes" ng-value="credentialsValue.value"> |
||||
|
{{credentialsValue.name | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="10" class="md-block"> |
||||
|
<md-checkbox flex aria-label="{{ 'extension.ssl' | translate }}" |
||||
|
ng-model="broker.ssl">{{ 'extension.ssl' | translate }} |
||||
|
</md-checkbox> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row" ng-if='broker.credentials.type == "basic"'> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['mqttUsername_' + brokerIndex].$touched && theForm['mqttUsername_' + brokerIndex].$invalid"> |
||||
|
<label translate>extension.username</label> |
||||
|
<input required name="mqttUsername_{{brokerIndex}}" ng-model="broker.credentials.username"> |
||||
|
<div ng-messages="theForm['mqttUsername_' + brokerIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['mqttPassword_' + brokerIndex].$touched && theForm['mqttPassword_' + brokerIndex].$invalid"> |
||||
|
<label translate>extension.password</label> |
||||
|
<input required name="mqttPassword_{{brokerIndex}}" ng-model="broker.credentials.password"> |
||||
|
<div ng-messages="theForm['mqttPassword_' + brokerIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="column" ng-if='broker.credentials.type == "cert.PEM"' class="dropdown-section"> |
||||
|
<div class="tb-container" ng-class="broker.credentials.caCertFileName ? 'ng-valid' : 'ng-invalid'"> |
||||
|
<label class="tb-label" translate>extension.ca-cert</label> |
||||
|
<div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "caCert")' class="tb-file-select-container"> |
||||
|
<div class="tb-file-clear-container"> |
||||
|
<md-button ng-click='clearFile(broker, "caCert")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
<div class="alert tb-flow-drop" flow-drop> |
||||
|
<label for="caCertSelect_{{brokerIndex}}" translate>extension.drop-file</label> |
||||
|
<input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="caCertSelect_{{brokerIndex}}"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="dropdown-messages"> |
||||
|
<div ng-if="!broker.credentials.caCertFileName" class="tb-error-message" translate>extension.no-file</div> |
||||
|
<div ng-if="broker.credentials.caCertFileName">{{broker.credentials.caCertFileName}}</div> |
||||
|
</div> |
||||
|
<div class="tb-container" ng-class="broker.credentials.privateKeyFileName ? 'ng-valid' : 'ng-invalid'"> |
||||
|
<label class="tb-label" translate>extension.private-key</label> |
||||
|
<div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "privateKey")' class="tb-file-select-container"> |
||||
|
<div class="tb-file-clear-container"> |
||||
|
<md-button ng-click='clearFile(broker, "privateKey")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
<div class="alert tb-flow-drop" flow-drop> |
||||
|
<label for="privateKeySelect_{{brokerIndex}}" translate>extension.drop-file</label> |
||||
|
<input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="privateKeySelect_{{brokerIndex}}"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="dropdown-messages"> |
||||
|
<div ng-if="!broker.credentials.privateKeyFileName" class="tb-error-message" translate>extension.no-file</div> |
||||
|
<div ng-if="broker.credentials.privateKeyFileName">{{broker.credentials.privateKeyFileName}}</div> |
||||
|
</div> |
||||
|
<div class="tb-container" ng-class="broker.credentials.certFileName ? 'ng-valid' : 'ng-invalid'"> |
||||
|
<label class="tb-label" translate>extension.cert</label> |
||||
|
<div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "Cert")' class="tb-file-select-container"> |
||||
|
<div class="tb-file-clear-container"> |
||||
|
<md-button ng-click='clearFile(broker, "Cert")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
<div class="alert tb-flow-drop" flow-drop> |
||||
|
<label for="CertSelect_{{brokerIndex}}" translate>extension.drop-file</label> |
||||
|
<input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="CertSelect_{{brokerIndex}}"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="dropdown-messages"> |
||||
|
<div ng-if="!broker.credentials.certFileName" class="tb-error-message" translate>extension.no-file</div> |
||||
|
<div ng-if="broker.credentials.certFileName">{{broker.credentials.certFileName}}</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<v-accordion id="mqtt-mapping-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-mapping-pane_{{brokerIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.mapping' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="broker.mapping.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(mapIndex,map) in broker.mapping"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeMap(map, broker.mapping)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['mqttConverterType_' + brokerIndex + mapIndex].$touched && theForm['mqttConverterType_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.converter-type</label> |
||||
|
<md-select required name="mqttConverterType_{{brokerIndex}}{{mapIndex}}" ng-model="map.converterType" ng-change="changeConverterType(map)"> |
||||
|
<md-option ng-repeat="(converterType, value) in types.mqttConverterTypes" ng-value="converterType"> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['mqttConverterType_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="60" class="md-block"> |
||||
|
<label translate>extension.topic-filter</label> |
||||
|
<input required name="mqttTopicFilter_{{brokerIndex}}{{mapIndex}}" ng-model="map.topicFilter"> |
||||
|
<div ng-messages="theForm['mqttTopicFilter_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<div ng-if='map.converterType =="json"' ng-init="map.converter.type = 'json'"> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$touched && theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.device-name-expression</label> |
||||
|
<md-select required name="mqttDeviceNameExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.nameExp" ng-change="changeNameExpression(map.converter)"> |
||||
|
<md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="map.converter.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block" md-is-error="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$touched && theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.json-name-expression</label> |
||||
|
<input required name="mqttJsonNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameJsonExpression"> |
||||
|
<div ng-messages="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="map.converter.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block" md-is-error="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$touched && theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.topic-name-expression</label> |
||||
|
<input required name="mqttTopicNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameTopicExpression"> |
||||
|
<div ng-messages="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$touched && theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.device-type-expression</label> |
||||
|
<md-select required name="mqttDeviceTypeExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.typeExp" ng-change="changeTypeExpression(map.converter)"> |
||||
|
<md-option ng-repeat="(key, value) in deviceTypeExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="map.converter.typeExp == 'deviceTypeJsonExpression'" flex="60" class="md-block" md-is-error="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$touched && theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.json-type-expression</label> |
||||
|
<input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression"> |
||||
|
<div ng-messages="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="map.converter.typeExp == 'deviceTypeTopicExpression'" flex="60" class="md-block" md-is-error="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$touched && theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.topic-type-expression</label> |
||||
|
<input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression"> |
||||
|
<div ng-messages="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block"> |
||||
|
<label translate>extension.timeout</label> |
||||
|
<input type="number" name="mqttTimeout_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.timeout" parse-to-null> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$touched && theForm['mqttFilterExpression' + brokerIndex + mapIndex].$invalid"> |
||||
|
<label translate>extension.filter-expression</label> |
||||
|
<input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression"> |
||||
|
<div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
</div> |
||||
|
|
||||
|
<div ng-if='map.converterType == "custom"'> |
||||
|
<div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div> |
||||
|
<div flex class="tb-extension-custom-transformer-panel"> |
||||
|
<div flex class="tb-extension-custom-transformer" |
||||
|
ui-ace="extensionCustomConverterOptions" |
||||
|
ng-model="map.converter" |
||||
|
name="mqttCustomConverter_{{brokerIndex}}{{mapIndex}}" |
||||
|
ng-change='validateCustomConverter(map.converter, "mqttCustomConverter_" + brokerIndex + mapIndex)' |
||||
|
required> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-error-messages" ng-messages="theForm['mqttCustomConverter_' + brokerIndex + mapIndex].$error" role="alert"> |
||||
|
<div ng-message="required" class="tb-error-message" translate>extension.converter-json-required</div> |
||||
|
<div ng-message="converterJSON" class="tb-error-message" translate>extension.converter-json-parse</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<v-accordion ng-if='map.converterType =="json"' id="mqtt-attributes-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-attributes-pane_{{brokerIndex}}{{mapIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.attributes' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="map.converter.attributes.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(attributeIndex, attribute) in map.converter.attributes"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, map.converter.attributes)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$invalid"> |
||||
|
<label translate>extension.key</label> |
||||
|
<input required name="mqttAttributeKey_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.key"> |
||||
|
<div ng-messages="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$invalid"> |
||||
|
<label translate>extension.type</label> |
||||
|
<md-select required name="mqttAttributeType_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.type"> |
||||
|
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> |
||||
|
{{attrTypeValue | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<md-input-container class="md-block" md-is-error="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$invalid"> |
||||
|
<label translate>extension.value</label> |
||||
|
<input required name="mqttAttributeValue_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.value"> |
||||
|
<div ng-messages="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addAttribute(map.converter.attributes)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-attribute</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion ng-if='map.converterType =="json"' id="mqtt-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-timeseries-pane_{{brokerIndex}}{{mapIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.timeseries' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="map.converter.timeseries.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in map.converter.timeseries"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, map.converter.timeseries)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block" md-is-error="theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$invalid"> |
||||
|
<label translate>extension.key</label> |
||||
|
<input required name="mqttTimeseriesKey_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.key"> |
||||
|
<div ng-messages="theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$invalid"> |
||||
|
<label translate>extension.type</label> |
||||
|
<md-select required name="mqttTimeseriesType_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.type"> |
||||
|
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> |
||||
|
{{attrTypeValue | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<md-input-container class="md-block" md-is-error="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$invalid"> |
||||
|
<label translate>extension.value</label> |
||||
|
<input required name="mqttTimeseriesValue_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> |
||||
|
<div ng-messages="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addAttribute(map.converter.timeseries)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-timeseries</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addMap(broker.mapping)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-map</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion id="mqtt-connect-requests-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-connect-requests-pane_{{brokerIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.connect-requests' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="broker.connectRequests.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(connectRequestIndex, connectRequest) in broker.connectRequests"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(connectRequest, broker.connectRequests)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.topic-filter</label> |
||||
|
<input required name="conRequestTopicFilter_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.topicFilter"> |
||||
|
<div ng-messages="theForm['conRequestTopicFilter_' + brokerIndex + connectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$touched && theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$invalid"> |
||||
|
<label translate>extension.device-name-expression</label> |
||||
|
<md-select required name="connectDeviceNameExpression_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.nameExp" ng-change="changeNameExpression(connectRequest, 'connect')"> |
||||
|
<md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="connectRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.json-name-expression</label> |
||||
|
<input required name="connectJsonNameExp_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.deviceNameJsonExpression"> |
||||
|
<div ng-messages="theForm['connectJsonNameExp_' + brokerIndex + connectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="connectRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.topic-name-expression</label> |
||||
|
<input required name="connectTopicNameExp_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.deviceNameTopicExpression"> |
||||
|
<div ng-messages="theForm['connectTopicNameExp_' + brokerIndex + connectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click='addConnectRequest(broker.connectRequests, "connect")' aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-connect-request</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion id="mqtt-disconnect-requests-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-disconnect-requests-pane_{{brokerIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.disconnect-requests' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="broker.disconnectRequests.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(disconnectRequestIndex, disconnectRequest) in broker.disconnectRequests"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(disconnectRequest, broker.disconnectRequests)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.topic-filter</label> |
||||
|
<input required name="disconRequestTopicFilter_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.topicFilter"> |
||||
|
<div ng-messages="theForm['disconRequestTopicFilter_' + brokerIndex + disconnectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$touched && theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$invalid"> |
||||
|
<label translate>extension.device-name-expression</label> |
||||
|
<md-select required name="disconnectDeviceNameExpression_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.nameExp" ng-change="changeNameExpression(disconnectRequest, 'disconnect')"> |
||||
|
<md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="disconnectRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.json-name-expression</label> |
||||
|
<input required name="disconnectJsonNameExp_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.deviceNameJsonExpression"> |
||||
|
<div ng-messages="theForm['disconnectJsonNameExp_' + brokerIndex + disconnectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="disconnectRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.topic-name-expression</label> |
||||
|
<input required name="disconnectTopicNameExp_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.deviceNameTopicExpression"> |
||||
|
<div ng-messages="theForm['disconnectTopicNameExp_' + brokerIndex + disconnectRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click='addConnectRequest(broker.disconnectRequests, "disconnect")' aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-disconnect-request</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion id="mqtt-attribute-requests-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-attribute-requests-pane_{{brokerIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.attribute-requests' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="broker.attributeRequests.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(attributeRequestIndex, attributeRequest) in broker.attributeRequests"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attributeRequest, broker.attributeRequests)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="80" class="md-block"> |
||||
|
<label translate>extension.topic-filter</label> |
||||
|
<input required name="attributeRequestTopicFilter_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.topicFilter"> |
||||
|
<div ng-messages="theForm['attributeRequestTopicFilter_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="20" class="md-block"> |
||||
|
<md-checkbox flex aria-label="{{ 'extension.client-scope' | translate }}" |
||||
|
ng-model="attributeRequest.clientScope">{{ 'extension.client-scope' | translate }} |
||||
|
</md-checkbox> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$invalid"> |
||||
|
<label translate>extension.device-name-expression</label> |
||||
|
<md-select required name="attrRequestDeviceNameExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.nameExp" ng-change="changeNameExpression(attributeRequest, 'attribute')"> |
||||
|
<md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="attributeRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.json-name-expression</label> |
||||
|
<input required name="attrRequestJsonNameExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.deviceNameJsonExpression"> |
||||
|
<div ng-messages="theForm['attrRequestJsonNameExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="attributeRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.topic-name-expression</label> |
||||
|
<input required name="attrRequestTopicNameExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.deviceNameTopicExpression"> |
||||
|
<div ng-messages="theForm['attrRequestTopicNameExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$invalid"> |
||||
|
<label translate>extension.attribute-key-expression</label> |
||||
|
<md-select required name="attrRequestAttributeKeyExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attrKey" ng-change="changeAttrKeyExpression(attributeRequest)"> |
||||
|
<md-option ng-repeat="(key, value) in attributeKeyExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="attributeRequest.attrKey == 'attributeKeyJsonExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.attr-json-key-expression</label> |
||||
|
<input required name="attrRequestJsonKeyExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attributeKeyJsonExpression"> |
||||
|
<div ng-messages="theForm['attrRequestJsonKeyExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="attributeRequest.attrKey == 'attributeKeyTopicExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.attr-topic-key-expression</label> |
||||
|
<input required name="attrRequestTopicKeyExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attributeKeyTopicExpression"> |
||||
|
<div ng-messages="theForm['attrRequestTopicKeyExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$invalid"> |
||||
|
<label translate>extension.request-id-expression</label> |
||||
|
<md-select required name="attrRequestIdExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestId" ng-change="changeRequestIdExpression(attributeRequest)"> |
||||
|
<md-option ng-repeat="(key, value) in requestIdExpressions" ng-value='key'> |
||||
|
{{value | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="attributeRequest.requestId == 'requestIdJsonExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.request-id-json-expression</label> |
||||
|
<input required name="attrRequestJsonIdExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestIdJsonExpression"> |
||||
|
<div ng-messages="theForm['attrRequestJsonIdExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container ng-if="attributeRequest.requestId == 'requestIdTopicExpression'" flex="60" class="md-block"> |
||||
|
<label translate>extension.request-id-topic-expression</label> |
||||
|
<input required name="attrRequestTopicIdExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestIdTopicExpression"> |
||||
|
<div ng-messages="theForm['attrRequestTopicIdExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.response-topic-expression</label> |
||||
|
<input required name="attributeRequestResponseTopicExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.responseTopicExpression"> |
||||
|
<div ng-messages="theForm['attributeRequestResponseTopicExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.value-expression</label> |
||||
|
<input required name="attributeRequestValueExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.valueExpression"> |
||||
|
<div ng-messages="theForm['attributeRequestValueExp_' + brokerIndex + attributeRequestIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addAttributeRequest(broker.attributeRequests)" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-attribute-request</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion id="mqtt-attribute-updates-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-attribute-updates-pane_{{brokerIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.attribute-updates' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="broker.attributeUpdates.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(attributeUpdateIndex, attributeUpdate) in broker.attributeUpdates"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attributeUpdate, broker.attributeUpdates)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.device-name-filter</label> |
||||
|
<input required name="attributeUpdateDeviceNameFilter_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.deviceNameFilter"> |
||||
|
<div ng-messages="theForm['attributeUpdateDeviceNameFilter_' + brokerIndex + attributeUpdateIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.attribute-filter</label> |
||||
|
<input required name="attributeUpdateAttributeFilter_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.attributeFilter"> |
||||
|
<div ng-messages="theForm['attributeUpdateAttributeFilter_' + brokerIndex + attributeUpdateIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.topic-expression</label> |
||||
|
<input required name="attributeUpdateTopicExp_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.topicExpression"> |
||||
|
<div ng-messages="theForm['attributeUpdateTopicExp_' + brokerIndex + attributeUpdateIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.value-expression</label> |
||||
|
<input required name="attributeUpdateValueExp_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.valueExpression"> |
||||
|
<div ng-messages="theForm['attributeUpdateValueExp_' + brokerIndex + attributeUpdateIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click='addAttributeUpdate(broker.attributeUpdates)' aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-attribute-update</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion id="mqtt-server-side-rpc-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="mqtt-server-side-rpc-pane_{{brokerIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.server-side-rpc' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="broker.serverSideRpc.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(rpcIndex, rpc) in broker.serverSideRpc"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(rpc, broker.serverSideRpc)"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.device-name-filter</label> |
||||
|
<input required name="serverSideRpcDeviceNameFilter_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.deviceNameFilter"> |
||||
|
<div ng-messages="theForm['serverSideRpcDeviceNameFilter_' + brokerIndex + rpcIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.method-filter</label> |
||||
|
<input required name="serverSideRpcMethodFilter_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.methodFilter"> |
||||
|
<div ng-messages="theForm['serverSideRpcMethodFilter_' + brokerIndex + rpcIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.request-topic-expression</label> |
||||
|
<input required name="serverSideRpcRequestTopicExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.requestTopicExpression"> |
||||
|
<div ng-messages="theForm['serverSideRpcRequestTopicExp_' + brokerIndex + rpcIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.response-topic-expression</label> |
||||
|
<input name="serverSideRpcResponseTopicExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.responseTopicExpression" parse-to-null> |
||||
|
</md-input-container> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.response-timeout</label> |
||||
|
<input type="number" name="serverSideRpcResponseTimeout_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.responseTimeout" parse-to-null> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.value-expression</label> |
||||
|
<input required name="serverSideRpcValueExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.valueExpression"> |
||||
|
<div ng-messages="theForm['serverSideRpcValueExp_' + brokerIndex + rpcIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click='addServerSideRpc(broker.serverSideRpc)' aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-server-side-rpc-request</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
|
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addBroker()" aria-label="{{ 'action.add' | translate }}"> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-broker</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
<!--<pre> |
||||
|
{{config | json}} |
||||
|
</pre>--> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
@ -0,0 +1,173 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 'brace/ext/language_tools'; |
||||
|
import 'brace/mode/json'; |
||||
|
import 'brace/theme/github'; |
||||
|
|
||||
|
import './extension-form.scss'; |
||||
|
|
||||
|
/* eslint-disable angular/log */ |
||||
|
|
||||
|
import extensionFormOpcTemplate from './extension-form-opc.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ExtensionFormOpcDirective($compile, $templateCache, $translate, types) { |
||||
|
|
||||
|
|
||||
|
var linker = function(scope, element) { |
||||
|
|
||||
|
|
||||
|
function Server() { |
||||
|
this.applicationName = "Thingsboard OPC-UA client"; |
||||
|
this.applicationUri = ""; |
||||
|
this.host = "localhost"; |
||||
|
this.port = 49320; |
||||
|
this.scanPeriodInSeconds = 10; |
||||
|
this.timeoutInMillis = 5000; |
||||
|
this.security = "Basic128Rsa15"; |
||||
|
this.identity = { |
||||
|
"type": "anonymous" |
||||
|
}; |
||||
|
this.keystore = { |
||||
|
"type": "PKCS12", |
||||
|
"location": "example.pfx", |
||||
|
"password": "secret", |
||||
|
"alias": "gateway", |
||||
|
"keyPassword": "secret" |
||||
|
}; |
||||
|
this.mapping = [] |
||||
|
} |
||||
|
|
||||
|
function Map() { |
||||
|
this.deviceNodePattern = "Channel1\\.Device\\d+$"; |
||||
|
this.deviceNamePattern = "Device ${_System._DeviceId}"; |
||||
|
this.attributes = []; |
||||
|
this.timeseries = []; |
||||
|
} |
||||
|
|
||||
|
function Attribute() { |
||||
|
this.key = "Tag1"; |
||||
|
this.type = "string"; |
||||
|
this.value = "${Tag1}"; |
||||
|
} |
||||
|
|
||||
|
function Timeseries() { |
||||
|
this.key = "Tag2"; |
||||
|
this.type = "long"; |
||||
|
this.value = "${Tag2}"; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
var template = $templateCache.get(extensionFormOpcTemplate); |
||||
|
element.html(template); |
||||
|
|
||||
|
scope.types = types; |
||||
|
scope.theForm = scope.$parent.theForm; |
||||
|
|
||||
|
|
||||
|
if (!scope.configuration.servers.length) { |
||||
|
scope.configuration.servers.push(new Server()); |
||||
|
} |
||||
|
|
||||
|
scope.addServer = function(serversList) { |
||||
|
serversList.push(new Server()); |
||||
|
// scope.addMap(serversList[serversList.length-1].mapping);
|
||||
|
|
||||
|
scope.theForm.$setDirty(); |
||||
|
}; |
||||
|
|
||||
|
scope.addMap = function(mappingList) { |
||||
|
mappingList.push(new Map()); |
||||
|
scope.theForm.$setDirty(); |
||||
|
}; |
||||
|
|
||||
|
scope.addNewAttribute = function(attributesList) { |
||||
|
attributesList.push(new Attribute()); |
||||
|
scope.theForm.$setDirty(); |
||||
|
}; |
||||
|
|
||||
|
scope.addNewTimeseries = function(timeseriesList) { |
||||
|
timeseriesList.push(new Timeseries()); |
||||
|
scope.theForm.$setDirty(); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
scope.removeItem = (item, itemList) => { |
||||
|
var index = itemList.indexOf(item); |
||||
|
if (index > -1) { |
||||
|
itemList.splice(index, 1); |
||||
|
} |
||||
|
scope.theForm.$setDirty(); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
$compile(element.contents())(scope); |
||||
|
|
||||
|
|
||||
|
scope.fileAdded = function($file, model, options) { |
||||
|
let reader = new FileReader(); |
||||
|
reader.onload = function(event) { |
||||
|
scope.$apply(function() { |
||||
|
if(event.target.result) { |
||||
|
scope.theForm.$setDirty(); |
||||
|
let addedFile = event.target.result; |
||||
|
|
||||
|
if (addedFile && addedFile.length > 0) { |
||||
|
model[options.fileName] = $file.name; |
||||
|
model[options.file] = addedFile.replace(/^data.*base64,/, ""); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
reader.readAsDataURL($file.file); |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
scope.clearFile = function(model, options) { |
||||
|
scope.theForm.$setDirty(); |
||||
|
|
||||
|
model[options.fileName] = null; |
||||
|
model[options.file] = null; |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
scope.collapseValidation = function(index, id) { |
||||
|
var invalidState = angular.element('#'+id+':has(.ng-invalid)'); |
||||
|
if(invalidState.length) { |
||||
|
invalidState.addClass('inner-invalid'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
scope.expandValidation = function (index, id) { |
||||
|
var invalidState = angular.element('#'+id); |
||||
|
invalidState.removeClass('inner-invalid'); |
||||
|
}; |
||||
|
|
||||
|
}; |
||||
|
|
||||
|
return { |
||||
|
restrict: "A", |
||||
|
link: linker, |
||||
|
scope: { |
||||
|
configuration: "=", |
||||
|
isAdd: "=" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,557 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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-card class="extension-form extension-opc"> |
||||
|
<md-card-title> |
||||
|
<md-card-title-text> |
||||
|
<span translate class="md-headline">extension.configuration</span> |
||||
|
</md-card-title-text> |
||||
|
</md-card-title> |
||||
|
|
||||
|
<md-card-content> |
||||
|
<v-accordion id="opc-server-configs-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="opc-servers-pane" expanded="true"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.opc-server' | translate }} |
||||
|
</v-pane-header> |
||||
|
|
||||
|
<v-pane-content> |
||||
|
<div ng-if="configuration.servers.length === 0"> |
||||
|
<span translate layout-align="center center" class="tb-prompt">extension.opc-add-server-prompt</span> |
||||
|
</div> |
||||
|
|
||||
|
<div ng-if="configuration.servers.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers"> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" |
||||
|
class="md-icon-button" |
||||
|
ng-click="removeItem(server, configuration.servers)" |
||||
|
ng-hide="configuration.servers.length < 2" |
||||
|
> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
|
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
|
||||
|
<div layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.opc-application-name</label> |
||||
|
<input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName"> |
||||
|
<div ng-messages="theForm['applicationName_' + serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
|
||||
|
<md-input-container flex="50" class="md-block" md-is-error="theForm['applicationUri_' + serverIndex].$touched && theForm['applicationUri_' + serverIndex].$invalid"> |
||||
|
<label translate>extension.opc-application-uri</label> |
||||
|
<input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri"> |
||||
|
<div ng-messages="theForm['applicationUri_' + serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.host</label> |
||||
|
<input required name="host_{{serverIndex}}" ng-model="server.host"> |
||||
|
<div ng-messages="theForm['host_' + serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.port</label> |
||||
|
<input type="number" |
||||
|
required |
||||
|
name="port_{{serverIndex}}" |
||||
|
ng-model="server.port" |
||||
|
min="1" |
||||
|
max="65535" |
||||
|
> |
||||
|
<div ng-messages="theForm['port_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
<div translate |
||||
|
ng-message="min" |
||||
|
>extension.port-range</div> |
||||
|
<div translate |
||||
|
ng-message="max" |
||||
|
>extension.port-range</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
<div layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.opc-scan-period-in-seconds</label> |
||||
|
<input type="number" |
||||
|
required |
||||
|
name="scanPeriodInSeconds_{{serverIndex}}" |
||||
|
ng-model="server.scanPeriodInSeconds"> |
||||
|
<div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.timeout</label> |
||||
|
<input type="number" |
||||
|
required name="timeoutInMillis_{{serverIndex}}" |
||||
|
ng-model="server.timeoutInMillis" |
||||
|
> |
||||
|
<div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
<div layout="row"> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block tb-container-for-select"> |
||||
|
<label translate>extension.opc-security</label> |
||||
|
<md-select required |
||||
|
name="securityType_{{serverIndex}}" |
||||
|
ng-model="server.security"> |
||||
|
<md-option ng-value="securityType" |
||||
|
ng-repeat="(securityType, securityValue) in types.extensionOpcSecurityTypes" |
||||
|
><span ng-bind="::securityValue"></span></md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['securityType_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block tb-container-for-select"> |
||||
|
<label translate>extension.opc-identity</label> |
||||
|
<md-select required |
||||
|
name="identityType_{{serverIndex}}" |
||||
|
ng-model="server.identity.type" |
||||
|
> |
||||
|
<md-option ng-value="identityType" |
||||
|
ng-repeat="(identityType, identityValue) in types.extensionIdentityType" |
||||
|
><span ng-bind="identityValue | translate"></span></md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['identityType_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
<div ng-if="server.identity.type != 'username'"> |
||||
|
<span class="" |
||||
|
ng-init="server.identity = {'type':'anonymous'}"></span> |
||||
|
</div> |
||||
|
<div layout="row" ng-if="server.identity.type == 'username'"> |
||||
|
<md-input-container flex="50" class="md-block" md-is-error="theForm['identityUsername_' + serverIndex].$touched && theForm['identityUsername_' + serverIndex].$invalid"> |
||||
|
<label translate>extension.username</label> |
||||
|
<input required |
||||
|
name="identityUsername_{{serverIndex}}" |
||||
|
ng-model="server.identity.username" |
||||
|
> |
||||
|
<div ng-messages="theForm['identityUsername_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block" md-is-error="theForm['identityPassword_' + serverIndex].$touched && theForm['identityPassword_' + serverIndex].$invalid"> |
||||
|
<label translate>extension.password</label> |
||||
|
<input required |
||||
|
name="identityPassword_{{serverIndex}}" ng-model="server.identity.password"> |
||||
|
<div ng-messages="theForm['identityPassword_' + serverIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
<v-accordion id="opc-keystore-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="opc-keystore-pane__{{serverIndex}}" expanded="true"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.opc-keystore' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
|
||||
|
<md-input-container class="md-block tb-container-for-select"> |
||||
|
<label translate>extension.opc-keystore-type</label> |
||||
|
<md-select required name="keystoreType_{{serverIndex}}" ng-model="server.keystore.type"> |
||||
|
<md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['keystoreType_'+serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<section class="dropdown-section"> |
||||
|
<div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}"> |
||||
|
<span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span> |
||||
|
<label class="tb-label" translate>extension.opc-keystore-location</label> |
||||
|
<div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container"> |
||||
|
<div class="tb-file-clear-container"> |
||||
|
<md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
<div class="alert tb-flow-drop" flow-drop> |
||||
|
<label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label> |
||||
|
<input flow-attrs="{accept:'.pfx,.p12'}" |
||||
|
type="file" |
||||
|
class="file-input" |
||||
|
flow-btn id="dropFileKeystore_{{serverIndex}}" |
||||
|
name="keystoreFile" |
||||
|
ng-model="server.keystore.file" |
||||
|
> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="dropdown-messages"> |
||||
|
<div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div> |
||||
|
<div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
|
||||
|
<div flex layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.opc-keystore-password</label> |
||||
|
<input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password"> |
||||
|
<div ng-messages="theForm['keystorePassword_' + serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.opc-keystore-alias</label> |
||||
|
<input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias"> |
||||
|
<div ng-messages="theForm['keystoreAlias_' + serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>extension.opc-keystore-key-password</label> |
||||
|
<input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword"> |
||||
|
<div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
|
||||
|
<v-accordion id="opc-mapping-accordion" |
||||
|
class="vAccordion--default" |
||||
|
onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="opc-mapping-pane_{{serverIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.mapping' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-if="server.mapping.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" |
||||
|
ng-repeat="(mapIndex, map) in server.mapping" |
||||
|
> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" |
||||
|
class="md-icon-button" |
||||
|
ng-click="removeItem(map, server.mapping)" |
||||
|
> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
|
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<div flex layout="row"> |
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.opc-device-node-pattern</label> |
||||
|
<input required |
||||
|
name="deviceNodePattern_{{serverIndex}}{{mapIndex}}" |
||||
|
ng-model="map.deviceNodePattern" |
||||
|
> |
||||
|
<div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
<md-input-container flex="50" class="md-block"> |
||||
|
<label translate>extension.opc-device-name-pattern</label> |
||||
|
<input required |
||||
|
name="deviceNamePattern_{{serverIndex}}{{mapIndex}}" |
||||
|
ng-model="map.deviceNamePattern" |
||||
|
> |
||||
|
<div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<v-accordion id="opc-attributes-accordion" |
||||
|
class="vAccordion--default" |
||||
|
onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="opc-attributes-pane_{{serverIndex}}{{mapIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.attributes' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-show="map.attributes.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" |
||||
|
ng-repeat="(attributeIndex, attribute) in map.attributes" |
||||
|
> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" |
||||
|
class="md-icon-button" |
||||
|
ng-click="removeItem(attribute, map.attributes)"> |
||||
|
<ng-md-icon icon="close" |
||||
|
aria-label="{{ 'action.remove' | translate }}" |
||||
|
></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
|
||||
|
<section flex |
||||
|
layout="row" |
||||
|
> |
||||
|
<md-input-container flex="60" class="md-block"> |
||||
|
<label translate>extension.key</label> |
||||
|
<input required |
||||
|
name="opcAttributeKey_{{serverIndex}}{{mapIndex}}{{attributeIndex}}" |
||||
|
ng-model="attribute.key" |
||||
|
> |
||||
|
<div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="40" class="md-block tb-container-for-select"> |
||||
|
<label translate>extension.type</label> |
||||
|
<md-select required name="opcAttributeType_{{serverIndex}}{{mapIndex}}{{attributeIndex}}" |
||||
|
ng-model="attribute.type" |
||||
|
> |
||||
|
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" |
||||
|
ng-value="attrType" |
||||
|
> |
||||
|
{{attrTypeValue | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['opcAttributeType_' + serverIndex + mapIndex + attributeIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
|
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="100" class="md-block"> |
||||
|
<label translate>extension.value</label> |
||||
|
<input required name="opcAttributeValue_{{serverIndex}}{{mapIndex}}{{attributeIndex}}" |
||||
|
ng-model="attribute.value" |
||||
|
> |
||||
|
<div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
|
||||
|
</section> |
||||
|
|
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addNewAttribute(map.attributes)" |
||||
|
aria-label="{{ 'action.add' | translate }}" |
||||
|
> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-attribute</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
<v-accordion id="opc-timeseries-accordion" class="vAccordion--default" onexpand="expandValidation(index, id)" oncollapse="collapseValidation(index, id)"> |
||||
|
<v-pane id="opc-timeseries-pane_{{serverIndex}}{{mapIndex}}"> |
||||
|
<v-pane-header> |
||||
|
{{ 'extension.timeseries' | translate }} |
||||
|
</v-pane-header> |
||||
|
<v-pane-content> |
||||
|
<div ng-show="map.timeseries.length > 0"> |
||||
|
<ol class="list-group"> |
||||
|
<li class="list-group-item" |
||||
|
ng-repeat="(timeseriesIndex, timeseries) in map.timeseries" |
||||
|
> |
||||
|
<md-button aria-label="{{ 'action.remove' | translate }}" |
||||
|
class="md-icon-button" |
||||
|
ng-click="removeItem(timeseries, map.timeseries)" |
||||
|
> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.remove' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-card> |
||||
|
<md-card-content> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="60" class="md-block"> |
||||
|
<label translate>extension.key</label> |
||||
|
<input required |
||||
|
name="opcTimeseriesKey_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" |
||||
|
ng-model="timeseries.key" |
||||
|
> |
||||
|
<div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container flex="40" |
||||
|
class="md-block tb-container-for-select" |
||||
|
> |
||||
|
<label translate>extension.type</label> |
||||
|
<md-select required |
||||
|
name="opcTimeseriesType_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" |
||||
|
ng-model="timeseries.type" |
||||
|
> |
||||
|
<md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" |
||||
|
ng-value="attrType" |
||||
|
> |
||||
|
{{attrTypeValue | translate}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
<div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error"> |
||||
|
<div translate |
||||
|
ng-message="required" |
||||
|
>extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
<section flex layout="row"> |
||||
|
<md-input-container flex="100" class="md-block"> |
||||
|
<label translate>extension.value</label> |
||||
|
<input required name="opcTimeseriesValue_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> |
||||
|
<div ng-messages="theForm['opcTimeseriesValue_' + serverIndex + mapIndex + timeseriesIndex].$error"> |
||||
|
<div translate ng-message="required">extension.field-required</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
</section> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex layout="row" layout-align="start center"> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addNewAttribute(map.timeseries)" |
||||
|
aria-label="{{ 'action.add' | translate }}" |
||||
|
> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-timeseries</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
</div> |
||||
|
<div flex |
||||
|
layout="row" |
||||
|
layout-align="start center" |
||||
|
> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addMap(server.mapping)" |
||||
|
aria-label="{{ 'action.add' | translate }}" |
||||
|
> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.add-map</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
|
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
|
</li> |
||||
|
</ol> |
||||
|
|
||||
|
<div flex |
||||
|
layout="row" |
||||
|
layout-align="start center" |
||||
|
> |
||||
|
<md-button class="md-primary md-raised" |
||||
|
ng-click="addServer(configuration.servers)" |
||||
|
aria-label="{{ 'action.add' | translate }}" |
||||
|
> |
||||
|
<md-icon class="material-icons">add</md-icon> |
||||
|
<span translate>extension.opc-add-server</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</v-pane-content> |
||||
|
</v-pane> |
||||
|
</v-accordion> |
||||
|
<!--{{config}}--> |
||||
|
</md-card-content> |
||||
|
</md-card> |
||||
@ -0,0 +1,76 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
.extension-form { |
||||
|
li > .md-button { |
||||
|
color: rgba(0, 0, 0, 0.7); |
||||
|
margin: 0; |
||||
|
} |
||||
|
.vAccordion--default { |
||||
|
margin-top: 0; |
||||
|
padding-left: 3px; |
||||
|
} |
||||
|
.tb-container { |
||||
|
width:100%; |
||||
|
} |
||||
|
.dropdown-messages { |
||||
|
.tb-error-message { |
||||
|
padding: 5px 0 0 0; |
||||
|
} |
||||
|
} |
||||
|
.dropdown-section { |
||||
|
margin-bottom: 30px; |
||||
|
} |
||||
|
v-pane.inner-invalid > v-pane-header { |
||||
|
border-bottom: 2px solid rgb(221,44,0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.extension-form.extension-mqtt { |
||||
|
md-checkbox{ |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-extension-custom-transformer-panel { |
||||
|
margin-left: 15px; |
||||
|
border: 1px solid #C0C0C0; |
||||
|
height: 100%; |
||||
|
.tb-extension-custom-transformer { |
||||
|
min-width: 600px; |
||||
|
min-height: 200px; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
.ace_text-input { |
||||
|
position:absolute!important |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.extensionDialog { |
||||
|
min-width: 1000px; |
||||
|
} |
||||
|
|
||||
|
.tb-container-for-select { |
||||
|
height: 58px; |
||||
|
} |
||||
|
|
||||
|
.tb-drop-file-input-hide { |
||||
|
height: 200%; |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue