thingsboard-developers

add opc-ua

11/23/2017 8:54:16 AM

Details

diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index dff7635..52c08d2 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -332,6 +332,20 @@ export default angular.module('thingsboard.types', [])
                 toDouble: 'extension.to-double',
                 custom: 'extension.custom'
             },
+            extensionOpcSecurityTypes: {
+                Basic128Rsa15: "Basic128Rsa15",
+                Basic256: "Basic256",
+                Basic256Sha256: "Basic256Sha256",
+                None: "None"
+            },
+            extensionIdentityType: {
+                anonymous: "anonymous",
+                username: "username"
+            },
+            extensionKeystoreType: {
+                PKCS12: "PKCS12",
+                JKS: "JKS"
+            },
             latestTelemetry: {
                 value: "LATEST_TELEMETRY",
                 name: "attribute.scope-latest-telemetry",
diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js
index 3b13214..cc8d7c3 100644
--- a/ui/src/app/extension/extension-dialog.controller.js
+++ b/ui/src/app/extension/extension-dialog.controller.js
@@ -29,45 +29,72 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
     vm.entityId = entityId;
     vm.allExtensions = allExtensions;
 
-    vm.configuration = {};
-    vm.newExtension = {id:"",type:"",configuration:vm.configuration};
 
-    if(!vm.isAdd) {
-        vm.newExtension = angular.copy(extension);
-        vm.configuration = vm.newExtension.configuration;
-        editTransformers(vm.newExtension);
+    if (extension) { // Editing
+        //vm.configuration = vm.extension.configuration;
+        vm.extension = angular.copy(extension);
+        editTransformers(vm.extension);
+    } else { // Add new
+        vm.extension = {};
     }
 
-    vm.cancel = cancel;
-    vm.save = save;
 
+    vm.extensionTypeChange = function () {
+        // $scope.theForm.$setPristine();
+        // $scope.theForm.$setUntouched();
+
+        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() {
         saveTransformers();
         if(vm.isAdd) {
-            vm.allExtensions.push(vm.newExtension);
+            vm.allExtensions.push(vm.extension);
         } else {
             var index = vm.allExtensions.indexOf(extension);
             if(index > -1) {
-                vm.allExtensions[index] = vm.newExtension;
+                vm.allExtensions[index] = vm.extension;
             }
         }
 
         var editedValue = angular.toJson(vm.allExtensions);
 
-        attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
-            function success() {
-                $scope.theForm.$setPristine();
-                $mdDialog.hide();
-            }
-        );
+        attributeService
+            .saveEntityAttributes(
+                vm.entityType,
+                vm.entityId,
+                types.attributesScope.shared.value,
+                [{key:"configuration", value:editedValue}]
+            )
+            .then(function success() {
+                    $scope.theForm.$setPristine();
+                    $mdDialog.hide();
+            });
     }
     
     vm.validateId = function() {
         var coincidenceArray = vm.allExtensions.filter(function(ext) {
-            return ext.id == vm.newExtension.id;
+            return ext.id == vm.extension.id;
         });
         if(coincidenceArray.length) {
             if(!vm.isAdd) {
@@ -82,11 +109,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
         } else {
             $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
         }
-    }
+    };
 
     function saveTransformers() {
-        var config = vm.newExtension.configuration.converterConfigurations;
-        if(vm.newExtension.type == types.extensionType.http) {
+        var config = vm.extension.configuration.converterConfigurations;
+        if(vm.extension.type == types.extensionType.http) {
             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++){
diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html
index edb6918..b4fac57 100644
--- a/ui/src/app/extension/extension-dialog.tpl.html
+++ b/ui/src/app/extension/extension-dialog.tpl.html
@@ -15,7 +15,7 @@
     limitations under the License.
 
 -->
-<md-dialog aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}" style="min-width: 1000px;">
+<md-dialog class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}">
     <form name="theForm" ng-submit="vm.save()">
         <md-toolbar>
             <div class="md-toolbar-tools">
@@ -26,8 +26,11 @@
                 </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">
@@ -35,41 +38,48 @@
                         <section flex layout="row">
                             <md-input-container flex="60" class="md-block">
                                 <label translate>extension.extension-id</label>
-                                <input required name="extensionId" ng-model="vm.newExtension.id" ng-change="vm.validateId()">
+                                <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.id-required</div>
                                     <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
                                 </div>
                             </md-input-container>
+
                             <md-input-container flex="40" class="md-block">
                                 <label translate>extension.extension-type</label>
-                                <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-model="vm.newExtension.type">
+
+                                <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.type-required</div>
                                 </div>
                             </md-input-container>
                         </section>
 
-                        <div tb-extension-form-http config="vm.configuration" is-add="vm.isAdd" ng-if="vm.newExtension.type && vm.newExtension.type == vm.types.extensionType.http"></div>
+                        <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-opc  configuration="vm.extension.configuration"                   ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
                     </fieldset>
-
-                    <!--<div>{{vm.newExtension}}</div>-->
+                    <!--<div>{{vm.extension}}</div>-->
                 </md-content>
             </div>
         </md-dialog-content>
+
         <md-dialog-actions layout="row">
             <span flex></span>
-            <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
+            <md-button type="submit"
+                       ng-disabled="loading || theForm.$invalid || !theForm.$dirty"
                        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>
\ No newline at end of file
+</md-dialog>
+
diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss
index cfea19f..142516c 100644
--- a/ui/src/app/extension/extensions-forms/extension-form.scss
+++ b/ui/src/app/extension/extensions-forms/extension-form.scss
@@ -37,4 +37,20 @@
   .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%;
 }
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js
new file mode 100644
index 0000000..7eeeb29
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-opc.directive.js
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+        };
+
+    };
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            configuration: "=",
+            isAdd: "="
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
index 779e93a..e19984e 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
@@ -15,4 +15,552 @@
     limitations under the License.
 
 -->
-<div>OPC UA</div>
\ No newline at end of file
+<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="http-server-configs-accordion" class="vAccordion--default">
+            <v-pane id="http-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.opc-field-required</div>
+                                                </div>
+                                            </md-input-container>
+
+
+                                            <md-input-container flex="50" class="md-block">
+                                                <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.opc-field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                        </div>
+
+
+                                        <div layout="row">
+                                            <md-input-container flex="50" class="md-block">
+                                                <label translate>extension.opc-host</label>
+                                                <input required name="host_{{serverIndex}}" ng-model="server.host">
+                                                <div ng-messages="theForm['host_' + serverIndex].$error">
+                                                    <div translate ng-message="required">extension.opc-field-required</div>
+                                                </div>
+                                            </md-input-container>
+
+                                            <md-input-container flex="50" class="md-block">
+                                                <label translate>extension.opc-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.opc-field-required</div>
+                                                    <div translate
+                                                         ng-message="min"
+                                                    >Port should be in a range from 1 to 65535</div>
+                                                    <div translate
+                                                         ng-message="max"
+                                                    >Port should be in a range from 1 to 65535</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.opc-field-required</div>
+                                                </div>
+                                            </md-input-container>
+
+                                            <md-input-container flex="50" class="md-block">
+                                                <label translate>extension.opc-timeout-in-millis</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.opc-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.opc-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"></span></md-option>
+                                                </md-select>
+                                                <div ng-messages="theForm['identityType_' + serverIndex].$error">
+                                                    <div translate
+                                                         ng-message="required"
+                                                    >extension.opc-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">
+                                                <label translate>extension.opc-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.opc-field-required</div>
+                                                </div>
+                                            </md-input-container>
+
+                                            <md-input-container flex="50" class="md-block">
+                                                <label translate>extension.opc-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.opc-field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                        </div>
+
+
+                                        <v-accordion id="opc-attributes-accordion" class="vAccordion--default">
+                                            <v-pane id="opc-attributes-pane">
+                                                <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.opc-field-required</div>
+                                                        </div>
+                                                    </md-input-container>
+
+                                                    <div class="tb-container">
+                                                        <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>
+                                                        <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>
+
+
+                                                    <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.opc-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.opc-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.opc-field-required</div>
+                                                        </div>
+                                                    </md-input-container>
+
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+
+                                        <v-accordion id="opc-attributes-accordion"
+                                                     class="vAccordion--default"
+                                        >
+                                            <v-pane id="opc-attributes-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.opc-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.opc-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.opc-field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </div>
+
+
+                                                                        <v-accordion id="opc-attributes-accordion"
+                                                                                     class="vAccordion--default"
+                                                                        >
+                                                                            <v-pane id="opc-attributes-pane">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.opc-mapping-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.opc-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.required-type</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.opc-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-tooltip md-direction="top">
+                                                                                                {{ 'extension.add-map' | translate }}
+                                                                                            </md-tooltip>
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>action.add</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+                                                                        <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
+                                                                            <v-pane id="opc-timeseries-pane">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.opc-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.opc-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.opc-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.required-value</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-tooltip md-direction="top">
+                                                                                                {{ 'extension.add-timeseries' | translate }}
+                                                                                            </md-tooltip>
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>action.add</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-tooltip md-direction="top">
+                                                                {{ 'extension.add-map' | translate }}
+                                                            </md-tooltip>
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>action.add</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-another-server</span>
+                            </md-button>
+                        </div>
+
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+        <!--{{config}}-->
+    </md-card-content>
+</md-card>
\ No newline at end of file
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js
index ae28d24..21d716b 100644
--- a/ui/src/app/extension/extension-table.directive.js
+++ b/ui/src/app/extension/extension-table.directive.js
@@ -126,11 +126,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
             controllerAs: 'vm',
             templateUrl: extensionDialogTemplate,
             parent: angular.element($document[0].body),
-            locals: { isAdd: isAdd,
-                      allExtensions: vm.allExtensions,
-                      entityId: vm.entityId,
-                      entityType: vm.entityType,
-                      extension: extension},
+            locals: {
+                isAdd: isAdd,
+                allExtensions: vm.allExtensions,
+                entityId: vm.entityId,
+                entityType: vm.entityType,
+                extension: extension
+            },
             bindToController: true,
             targetEvent: $event,
             fullscreen: true,
diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js
index 78fd56d..4c9f812 100644
--- a/ui/src/app/extension/index.js
+++ b/ui/src/app/extension/index.js
@@ -16,10 +16,12 @@
 
 import ExtensionTableDirective from './extension-table.directive';
 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
+import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
 import {ParseToNull} from './extension-dialog.controller';
 
 export default angular.module('thingsboard.extension', [])
     .directive('tbExtensionTable', ExtensionTableDirective)
     .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
+    .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
     .directive('parseToNull', ParseToNull)
     .name;
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index d8b861f..6f626b5 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -773,14 +773,44 @@ export default angular.module('thingsboard.locale', [])
                     "json-parse": "Unable to parse transformer json.",
                     "attributes": "Attributes",
                     "add-attribute": "Add attribute",
+                    "add-map": "Add mapping element",
                     "timeseries": "Timeseries",
                     "add-timeseries": "Add timeseries",
+
+                    "opc-field-required": "Field is required",
+                    "opc-server": "Servers",
+                    "opc-add-server-hint": "Add server",
+                    "opc-add-server-prompt": "Please add server",
+                    "opc-server-id": "Server id",
+                    "opc-timeseries": "Timeseries",
+                    "opc-application-name": "Application name",
+                    "opc-application-uri": "Application uri",
+                    "opc-host": "Host",
+                    "opc-port": "Port",
+                    "opc-scan-period-in-seconds": "Scan period in seconds",
+                    "opc-timeout-in-millis": "Timeout in milliseconds",
+                    "opc-security": "Security",
+                    "opc-identity": "Identity",
+                    "opc-keystore": "Keystore",
+                    "opc-type": "Type",
+                    "opc-keystore-type":"Type",
+                    "opc-keystore-location":"Location *",
+                    "opc-keystore-password":"Password",
+                    "opc-keystore-alias":"Alias",
+                    "opc-keystore-key-password":"Key password",
+                    "opc-mapping":"Mapping",
+                    "opc-device-node-pattern":"Device node pattern",
+                    "opc-device-name-pattern":"Device name pattern",
+                    "opc-mapping-attributes":"Mapping attributes",
+                    "opc-username":"Username",
+                    "opc-password":"Password",
+                    "opc-add-another-server":"Add another server",
                 },
                 "fullscreen": {
                     "expand": "Expand to fullscreen",
                     "exit": "Exit fullscreen",
                     "toggle": "Toggle fullscreen mode",
-                    "fullscreen": "Fullscreen"
+                    "fullscreen": "Fullscreen",
                 },
                 "function": {
                     "function": "Function"