Commit cc302f18010bf8f2fce940ed8b771ec7d8eba3df

Authored by oleg
1 parent 8af217a9

add opc-ua

... ... @@ -332,6 +332,20 @@ export default angular.module('thingsboard.types', [])
332 332 toDouble: 'extension.to-double',
333 333 custom: 'extension.custom'
334 334 },
  335 + extensionOpcSecurityTypes: {
  336 + Basic128Rsa15: "Basic128Rsa15",
  337 + Basic256: "Basic256",
  338 + Basic256Sha256: "Basic256Sha256",
  339 + None: "None"
  340 + },
  341 + extensionIdentityType: {
  342 + anonymous: "anonymous",
  343 + username: "username"
  344 + },
  345 + extensionKeystoreType: {
  346 + PKCS12: "PKCS12",
  347 + JKS: "JKS"
  348 + },
335 349 latestTelemetry: {
336 350 value: "LATEST_TELEMETRY",
337 351 name: "attribute.scope-latest-telemetry",
... ...
... ... @@ -29,45 +29,72 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
29 29 vm.entityId = entityId;
30 30 vm.allExtensions = allExtensions;
31 31
32   - vm.configuration = {};
33   - vm.newExtension = {id:"",type:"",configuration:vm.configuration};
34 32
35   - if(!vm.isAdd) {
36   - vm.newExtension = angular.copy(extension);
37   - vm.configuration = vm.newExtension.configuration;
38   - editTransformers(vm.newExtension);
  33 + if (extension) { // Editing
  34 + //vm.configuration = vm.extension.configuration;
  35 + vm.extension = angular.copy(extension);
  36 + editTransformers(vm.extension);
  37 + } else { // Add new
  38 + vm.extension = {};
39 39 }
40 40
41   - vm.cancel = cancel;
42   - vm.save = save;
43 41
  42 + vm.extensionTypeChange = function () {
  43 + // $scope.theForm.$setPristine();
  44 + // $scope.theForm.$setUntouched();
  45 +
  46 + if (vm.extension.type === "HTTP") {
  47 + vm.extension.configuration = {
  48 + "converterConfigurations": []
  49 + };
  50 + }
  51 + if (vm.extension.type === "MQTT") {
  52 + vm.extension.configuration = {
  53 + "brokers": []
  54 + };
  55 + }
  56 + if (vm.extension.type === "OPC UA") {
  57 + vm.extension.configuration = {
  58 + "servers": []
  59 + };
  60 + }
  61 + };
  62 +
  63 + vm.cancel = cancel;
44 64 function cancel() {
45 65 $mdDialog.cancel();
46 66 }
  67 +
  68 + vm.save = save;
47 69 function save() {
48 70 saveTransformers();
49 71 if(vm.isAdd) {
50   - vm.allExtensions.push(vm.newExtension);
  72 + vm.allExtensions.push(vm.extension);
51 73 } else {
52 74 var index = vm.allExtensions.indexOf(extension);
53 75 if(index > -1) {
54   - vm.allExtensions[index] = vm.newExtension;
  76 + vm.allExtensions[index] = vm.extension;
55 77 }
56 78 }
57 79
58 80 var editedValue = angular.toJson(vm.allExtensions);
59 81
60   - attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
61   - function success() {
62   - $scope.theForm.$setPristine();
63   - $mdDialog.hide();
64   - }
65   - );
  82 + attributeService
  83 + .saveEntityAttributes(
  84 + vm.entityType,
  85 + vm.entityId,
  86 + types.attributesScope.shared.value,
  87 + [{key:"configuration", value:editedValue}]
  88 + )
  89 + .then(function success() {
  90 + $scope.theForm.$setPristine();
  91 + $mdDialog.hide();
  92 + });
66 93 }
67 94
68 95 vm.validateId = function() {
69 96 var coincidenceArray = vm.allExtensions.filter(function(ext) {
70   - return ext.id == vm.newExtension.id;
  97 + return ext.id == vm.extension.id;
71 98 });
72 99 if(coincidenceArray.length) {
73 100 if(!vm.isAdd) {
... ... @@ -82,11 +109,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
82 109 } else {
83 110 $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
84 111 }
85   - }
  112 + };
86 113
87 114 function saveTransformers() {
88   - var config = vm.newExtension.configuration.converterConfigurations;
89   - if(vm.newExtension.type == types.extensionType.http) {
  115 + var config = vm.extension.configuration.converterConfigurations;
  116 + if(vm.extension.type == types.extensionType.http) {
90 117 for(let i=0;i<config.length;i++) {
91 118 for(let j=0;j<config[i].converters.length;j++){
92 119 for(let k=0;k<config[i].converters[j].attributes.length;k++){
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-dialog aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}" style="min-width: 1000px;">
  18 +<md-dialog class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}">
19 19 <form name="theForm" ng-submit="vm.save()">
20 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
... ... @@ -26,8 +26,11 @@
26 26 </md-button>
27 27 </div>
28 28 </md-toolbar>
  29 +
29 30 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  31 +
30 32 <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  33 +
31 34 <md-dialog-content>
32 35 <div class="md-dialog-content">
33 36 <md-content class="md-padding" layout="column">
... ... @@ -35,41 +38,48 @@
35 38 <section flex layout="row">
36 39 <md-input-container flex="60" class="md-block">
37 40 <label translate>extension.extension-id</label>
38   - <input required name="extensionId" ng-model="vm.newExtension.id" ng-change="vm.validateId()">
  41 + <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
39 42 <div ng-messages="theForm.extensionId.$error">
40 43 <div translate ng-message="required">extension.id-required</div>
41 44 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
42 45 </div>
43 46 </md-input-container>
  47 +
44 48 <md-input-container flex="40" class="md-block">
45 49 <label translate>extension.extension-type</label>
46   - <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-model="vm.newExtension.type">
  50 +
  51 + <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
47 52 <md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value">
48 53 {{value}}
49 54 </md-option>
50 55 </md-select>
  56 +
51 57 <div ng-messages="theForm.extensionType.$error">
52 58 <div translate ng-message="required">extension.type-required</div>
53 59 </div>
54 60 </md-input-container>
55 61 </section>
56 62
57   - <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>
  63 + <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>
58 64
  65 + <div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
59 66 </fieldset>
60   -
61   - <!--<div>{{vm.newExtension}}</div>-->
  67 + <!--<div>{{vm.extension}}</div>-->
62 68 </md-content>
63 69 </div>
64 70 </md-dialog-content>
  71 +
65 72 <md-dialog-actions layout="row">
66 73 <span flex></span>
67   - <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
  74 + <md-button type="submit"
  75 + ng-disabled="loading || theForm.$invalid || !theForm.$dirty"
68 76 class="md-raised md-primary">
69 77 {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
70 78 </md-button>
  79 +
71 80 <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}
72 81 </md-button>
73 82 </md-dialog-actions>
74 83 </form>
75   -</md-dialog>
\ No newline at end of file
  84 +</md-dialog>
  85 +
... ...
... ... @@ -126,11 +126,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
126 126 controllerAs: 'vm',
127 127 templateUrl: extensionDialogTemplate,
128 128 parent: angular.element($document[0].body),
129   - locals: { isAdd: isAdd,
130   - allExtensions: vm.allExtensions,
131   - entityId: vm.entityId,
132   - entityType: vm.entityType,
133   - extension: extension},
  129 + locals: {
  130 + isAdd: isAdd,
  131 + allExtensions: vm.allExtensions,
  132 + entityId: vm.entityId,
  133 + entityType: vm.entityType,
  134 + extension: extension
  135 + },
134 136 bindToController: true,
135 137 targetEvent: $event,
136 138 fullscreen: true,
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'brace/ext/language_tools';
  18 +import 'brace/mode/json';
  19 +import 'brace/theme/github';
  20 +
  21 +import './extension-form.scss';
  22 +
  23 +/* eslint-disable angular/log */
  24 +
  25 +import extensionFormOpcTemplate from './extension-form-opc.tpl.html';
  26 +
  27 +/* eslint-enable import/no-unresolved, import/default */
  28 +
  29 +/*@ngInject*/
  30 +export default function ExtensionFormOpcDirective($compile, $templateCache, $translate, types) {
  31 +
  32 +
  33 + var linker = function(scope, element) {
  34 +
  35 +
  36 + function Server() {
  37 + this.applicationName = "Thingsboard OPC-UA client";
  38 + this.applicationUri = "";
  39 + this.host = "localhost";
  40 + this.port = 49320;
  41 + this.scanPeriodInSeconds = 10;
  42 + this.timeoutInMillis = 5000;
  43 + this.security = "Basic128Rsa15";
  44 + this.identity = {
  45 + "type": "anonymous"
  46 + };
  47 + this.keystore = {
  48 + "type": "PKCS12",
  49 + "location": "example.pfx",
  50 + "password": "secret",
  51 + "alias": "gateway",
  52 + "keyPassword": "secret"
  53 + };
  54 + this.mapping = []
  55 + }
  56 +
  57 + function Map() {
  58 + this.deviceNodePattern = "Channel1\\.Device\\d+$";
  59 + this.deviceNamePattern = "Device ${_System._DeviceId}";
  60 + this.attributes = [];
  61 + this.timeseries = [];
  62 + }
  63 +
  64 + function Attribute() {
  65 + this.key = "Tag1";
  66 + this.type = "string";
  67 + this.value = "${Tag1}";
  68 + }
  69 +
  70 + function Timeseries() {
  71 + this.key = "Tag2";
  72 + this.type = "long";
  73 + this.value = "${Tag2}";
  74 + }
  75 +
  76 +
  77 + var template = $templateCache.get(extensionFormOpcTemplate);
  78 + element.html(template);
  79 +
  80 + scope.types = types;
  81 + scope.theForm = scope.$parent.theForm;
  82 +
  83 +
  84 + if (!scope.configuration.servers.length) {
  85 + scope.configuration.servers.push(new Server());
  86 + }
  87 +
  88 + scope.addServer = function(serversList) {
  89 + serversList.push(new Server());
  90 + // scope.addMap(serversList[serversList.length-1].mapping);
  91 +
  92 + scope.theForm.$setDirty();
  93 + };
  94 +
  95 + scope.addMap = function(mappingList) {
  96 + mappingList.push(new Map());
  97 + scope.theForm.$setDirty();
  98 + };
  99 +
  100 + scope.addNewAttribute = function(attributesList) {
  101 + attributesList.push(new Attribute());
  102 + scope.theForm.$setDirty();
  103 + };
  104 +
  105 + scope.addNewTimeseries = function(timeseriesList) {
  106 + timeseriesList.push(new Timeseries());
  107 + scope.theForm.$setDirty();
  108 + };
  109 +
  110 +
  111 + scope.removeItem = (item, itemList) => {
  112 + var index = itemList.indexOf(item);
  113 + if (index > -1) {
  114 + itemList.splice(index, 1);
  115 + }
  116 + scope.theForm.$setDirty();
  117 + };
  118 +
  119 +
  120 + $compile(element.contents())(scope);
  121 +
  122 +
  123 + scope.fileAdded = function($file, model, options) {
  124 + let reader = new FileReader();
  125 + reader.onload = function(event) {
  126 + scope.$apply(function() {
  127 + if(event.target.result) {
  128 + scope.theForm.$setDirty();
  129 + let addedFile = event.target.result;
  130 +
  131 + if (addedFile && addedFile.length > 0) {
  132 + model[options.fileName] = $file.name;
  133 + model[options.file] = addedFile.replace(/^data.*base64,/, "");
  134 +
  135 + }
  136 + }
  137 + });
  138 + };
  139 + reader.readAsDataURL($file.file);
  140 +
  141 + };
  142 +
  143 + scope.clearFile = function(model, options) {
  144 + scope.theForm.$setDirty();
  145 +
  146 + model[options.fileName] = null;
  147 + model[options.file] = null;
  148 +
  149 + };
  150 +
  151 + };
  152 +
  153 + return {
  154 + restrict: "A",
  155 + link: linker,
  156 + scope: {
  157 + configuration: "=",
  158 + isAdd: "="
  159 + }
  160 + }
  161 +}
\ No newline at end of file
... ...
... ... @@ -15,4 +15,552 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div>OPC UA</div>
\ No newline at end of file
  18 +<md-card class="extension-form extension-opc">
  19 + <md-card-title>
  20 + <md-card-title-text>
  21 + <span translate class="md-headline">extension.configuration</span>
  22 + </md-card-title-text>
  23 + </md-card-title>
  24 +
  25 + <md-card-content>
  26 + <v-accordion id="http-server-configs-accordion" class="vAccordion--default">
  27 + <v-pane id="http-servers-pane" expanded="true">
  28 + <v-pane-header>
  29 + {{ 'extension.opc-server' | translate }}
  30 + </v-pane-header>
  31 +
  32 + <v-pane-content>
  33 + <div ng-if="configuration.servers.length === 0">
  34 + <span translate layout-align="center center" class="tb-prompt">extension.opc-add-server-prompt</span>
  35 + </div>
  36 +
  37 + <div ng-if="configuration.servers.length > 0">
  38 + <ol class="list-group">
  39 + <li class="list-group-item" ng-repeat="(serverIndex, server) in configuration.servers">
  40 + <md-button aria-label="{{ 'action.remove' | translate }}"
  41 + class="md-icon-button"
  42 + ng-click="removeItem(server, configuration.servers)"
  43 + ng-hide="configuration.servers.length < 2"
  44 + >
  45 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  46 + <md-tooltip md-direction="top">
  47 + {{ 'action.remove' | translate }}
  48 + </md-tooltip>
  49 + </md-button>
  50 +
  51 + <md-card>
  52 + <md-card-content>
  53 +
  54 + <div layout="row">
  55 + <md-input-container flex="50" class="md-block">
  56 + <label translate>extension.opc-application-name</label>
  57 + <input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
  58 + <div ng-messages="theForm['applicationName_' + serverIndex].$error">
  59 + <div translate ng-message="required">extension.opc-field-required</div>
  60 + </div>
  61 + </md-input-container>
  62 +
  63 +
  64 + <md-input-container flex="50" class="md-block">
  65 + <label translate>extension.opc-application-uri</label>
  66 + <input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
  67 + <div ng-messages="theForm['applicationUri_' + serverIndex].$error">
  68 + <div translate ng-message="required">extension.opc-field-required</div>
  69 + </div>
  70 + </md-input-container>
  71 + </div>
  72 +
  73 +
  74 + <div layout="row">
  75 + <md-input-container flex="50" class="md-block">
  76 + <label translate>extension.opc-host</label>
  77 + <input required name="host_{{serverIndex}}" ng-model="server.host">
  78 + <div ng-messages="theForm['host_' + serverIndex].$error">
  79 + <div translate ng-message="required">extension.opc-field-required</div>
  80 + </div>
  81 + </md-input-container>
  82 +
  83 + <md-input-container flex="50" class="md-block">
  84 + <label translate>extension.opc-port</label>
  85 + <input type="number"
  86 + required
  87 + name="port_{{serverIndex}}"
  88 + ng-model="server.port"
  89 + min="1"
  90 + max="65535"
  91 + >
  92 + <div ng-messages="theForm['port_' + serverIndex].$error">
  93 + <div translate
  94 + ng-message="required"
  95 + >extension.opc-field-required</div>
  96 + <div translate
  97 + ng-message="min"
  98 + >Port should be in a range from 1 to 65535</div>
  99 + <div translate
  100 + ng-message="max"
  101 + >Port should be in a range from 1 to 65535</div>
  102 + </div>
  103 + </md-input-container>
  104 + </div>
  105 +
  106 + <div layout="row">
  107 + <md-input-container flex="50" class="md-block">
  108 + <label translate>extension.opc-scan-period-in-seconds</label>
  109 + <input type="number"
  110 + required
  111 + name="scanPeriodInSeconds_{{serverIndex}}"
  112 + ng-model="server.scanPeriodInSeconds">
  113 + <div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
  114 + <div translate
  115 + ng-message="required"
  116 + >extension.opc-field-required</div>
  117 + </div>
  118 + </md-input-container>
  119 +
  120 + <md-input-container flex="50" class="md-block">
  121 + <label translate>extension.opc-timeout-in-millis</label>
  122 + <input type="number"
  123 + required name="timeoutInMillis_{{serverIndex}}"
  124 + ng-model="server.timeoutInMillis"
  125 + >
  126 + <div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
  127 + <div translate
  128 + ng-message="required"
  129 + >extension.opc-field-required</div>
  130 + </div>
  131 + </md-input-container>
  132 + </div>
  133 +
  134 + <div layout="row">
  135 +
  136 + <md-input-container flex="50" class="md-block tb-container-for-select">
  137 + <label translate>extension.opc-security</label>
  138 + <md-select required
  139 + name="securityType_{{serverIndex}}"
  140 + ng-model="server.security">
  141 + <md-option ng-value="securityType"
  142 + ng-repeat="(securityType, securityValue) in types.extensionOpcSecurityTypes"
  143 + ><span ng-bind="::securityValue"></span></md-option>
  144 + </md-select>
  145 + <div ng-messages="theForm['securityType_' + serverIndex].$error">
  146 + <div translate
  147 + ng-message="required"
  148 + >extension.opc-field-required</div>
  149 + </div>
  150 + </md-input-container>
  151 +
  152 + <md-input-container flex="50" class="md-block tb-container-for-select">
  153 + <label translate>extension.opc-identity</label>
  154 + <md-select required
  155 + name="identityType_{{serverIndex}}"
  156 + ng-model="server.identity.type"
  157 + >
  158 + <md-option ng-value="identityType"
  159 + ng-repeat="(identityType, identityValue) in types.extensionIdentityType"
  160 + ><span ng-bind="::identityValue"></span></md-option>
  161 + </md-select>
  162 + <div ng-messages="theForm['identityType_' + serverIndex].$error">
  163 + <div translate
  164 + ng-message="required"
  165 + >extension.opc-field-required</div>
  166 + </div>
  167 + </md-input-container>
  168 + </div>
  169 +
  170 +
  171 + <div ng-if="server.identity.type != 'username'">
  172 + <span class=""
  173 + ng-init="server.identity = {'type':'anonymous'}"></span>
  174 + </div>
  175 + <div layout="row" ng-if="server.identity.type == 'username'">
  176 + <md-input-container flex="50" class="md-block">
  177 + <label translate>extension.opc-username</label>
  178 + <input required
  179 + name="identityUsername_{{serverIndex}}"
  180 + ng-model="server.identity.username"
  181 + >
  182 + <div ng-messages="theForm['identityUsername_' + serverIndex].$error">
  183 + <div translate
  184 + ng-message="required"
  185 + >extension.opc-field-required</div>
  186 + </div>
  187 + </md-input-container>
  188 +
  189 + <md-input-container flex="50" class="md-block">
  190 + <label translate>extension.opc-password</label>
  191 + <input required
  192 + name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
  193 + <div ng-messages="theForm['identityPassword_' + serverIndex].$error">
  194 + <div translate
  195 + ng-message="required"
  196 + >extension.opc-field-required</div>
  197 + </div>
  198 + </md-input-container>
  199 + </div>
  200 +
  201 +
  202 + <v-accordion id="opc-attributes-accordion" class="vAccordion--default">
  203 + <v-pane id="opc-attributes-pane">
  204 + <v-pane-header>
  205 + {{ 'extension.opc-keystore' | translate }}
  206 + </v-pane-header>
  207 + <v-pane-content>
  208 +
  209 + <md-input-container class="md-block tb-container-for-select">
  210 + <label translate>extension.opc-keystore-type</label>
  211 + <md-select required name="keystoreType_{{serverIndex}}" ng-model="server.keystore.type">
  212 + <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
  213 + </md-select>
  214 + <div ng-messages="theForm['keystoreType_'+serverIndex].$error">
  215 + <div translate ng-message="required">extension.opc-field-required</div>
  216 + </div>
  217 + </md-input-container>
  218 +
  219 + <div class="tb-container">
  220 + <span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
  221 + <label class="tb-label" translate>extension.opc-keystore-location</label>
  222 + <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
  223 + <div class="tb-file-clear-container">
  224 + <md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
  225 + <md-tooltip md-direction="top">
  226 + {{ 'action.remove' | translate }}
  227 + </md-tooltip>
  228 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
  229 + </md-button>
  230 + </div>
  231 + <div class="alert tb-flow-drop" flow-drop>
  232 + <label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
  233 + <input flow-attrs="{accept:'.pfx,.p12'}"
  234 + type="file"
  235 + class="file-input"
  236 + flow-btn id="dropFileKeystore_{{serverIndex}}"
  237 + name="keystoreFile"
  238 + ng-model="server.keystore.file"
  239 + >
  240 + </div>
  241 + </div>
  242 + </div>
  243 + <div>
  244 + <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
  245 + <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
  246 + </div>
  247 +
  248 +
  249 + <div flex layout="row">
  250 + <md-input-container flex="50" class="md-block">
  251 + <label translate>extension.opc-keystore-password</label>
  252 + <input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
  253 + <div ng-messages="theForm['keystorePassword_' + serverIndex].$error">
  254 + <div translate ng-message="required">extension.opc-field-required</div>
  255 + </div>
  256 + </md-input-container>
  257 +
  258 + <md-input-container flex="50" class="md-block">
  259 + <label translate>extension.opc-keystore-alias</label>
  260 + <input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
  261 + <div ng-messages="theForm['keystoreAlias_' + serverIndex].$error">
  262 + <div translate ng-message="required">extension.opc-field-required</div>
  263 + </div>
  264 + </md-input-container>
  265 + </div>
  266 +
  267 + <md-input-container class="md-block">
  268 + <label translate>extension.opc-keystore-key-password</label>
  269 + <input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
  270 + <div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error">
  271 + <div translate ng-message="required">extension.opc-field-required</div>
  272 + </div>
  273 + </md-input-container>
  274 +
  275 + </v-pane-content>
  276 + </v-pane>
  277 + </v-accordion>
  278 +
  279 +
  280 + <v-accordion id="opc-attributes-accordion"
  281 + class="vAccordion--default"
  282 + >
  283 + <v-pane id="opc-attributes-pane">
  284 + <v-pane-header>
  285 + {{ 'extension.opc-mapping' | translate }}
  286 + </v-pane-header>
  287 + <v-pane-content>
  288 + <div ng-if="server.mapping.length > 0">
  289 + <ol class="list-group">
  290 + <li class="list-group-item"
  291 + ng-repeat="(mapIndex, map) in server.mapping"
  292 + >
  293 + <md-button aria-label="{{ 'action.remove' | translate }}"
  294 + class="md-icon-button"
  295 + ng-click="removeItem(map, server.mapping)"
  296 + >
  297 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  298 + <md-tooltip md-direction="top">
  299 + {{ 'action.remove' | translate }}
  300 + </md-tooltip>
  301 + </md-button>
  302 +
  303 + <md-card>
  304 + <md-card-content>
  305 + <div flex layout="row">
  306 + <md-input-container flex="50" class="md-block">
  307 + <label translate>extension.opc-device-node-pattern</label>
  308 + <input required
  309 + name="deviceNodePattern_{{serverIndex}}{{mapIndex}}"
  310 + ng-model="map.deviceNodePattern"
  311 + >
  312 + <div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
  313 + <div translate
  314 + ng-message="required"
  315 + >extension.opc-field-required</div>
  316 + </div>
  317 + </md-input-container>
  318 +
  319 + <md-input-container flex="50" class="md-block">
  320 + <label translate>extension.opc-device-name-pattern</label>
  321 + <input required
  322 + name="deviceNamePattern_{{serverIndex}}{{mapIndex}}"
  323 + ng-model="map.deviceNamePattern"
  324 + >
  325 + <div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
  326 + <div translate
  327 + ng-message="required"
  328 + >extension.opc-field-required</div>
  329 + </div>
  330 + </md-input-container>
  331 + </div>
  332 +
  333 +
  334 + <v-accordion id="opc-attributes-accordion"
  335 + class="vAccordion--default"
  336 + >
  337 + <v-pane id="opc-attributes-pane">
  338 + <v-pane-header>
  339 + {{ 'extension.opc-mapping-attributes' | translate }}
  340 + </v-pane-header>
  341 + <v-pane-content>
  342 + <div ng-show="map.attributes.length > 0">
  343 + <ol class="list-group">
  344 + <li class="list-group-item"
  345 + ng-repeat="(attributeIndex, attribute) in map.attributes"
  346 + >
  347 + <md-button aria-label="{{ 'action.remove' | translate }}"
  348 + class="md-icon-button"
  349 + ng-click="removeItem(attribute, map.attributes)">
  350 + <ng-md-icon icon="close"
  351 + aria-label="{{ 'action.remove' | translate }}"
  352 + ></ng-md-icon>
  353 + <md-tooltip md-direction="top">
  354 + {{ 'action.remove' | translate }}
  355 + </md-tooltip>
  356 + </md-button>
  357 + <md-card>
  358 + <md-card-content>
  359 +
  360 + <section flex
  361 + layout="row"
  362 + >
  363 + <md-input-container flex="60" class="md-block">
  364 + <label translate>extension.key</label>
  365 + <input required
  366 + name="opcAttributeKey_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
  367 + ng-model="attribute.key"
  368 + >
  369 + <div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
  370 + <div translate
  371 + ng-message="required"
  372 + >extension.opc-field-required</div>
  373 + </div>
  374 + </md-input-container>
  375 + <md-input-container flex="40" class="md-block tb-container-for-select">
  376 + <label translate>extension.type</label>
  377 + <md-select required name="opcAttributeType_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
  378 + ng-model="attribute.type"
  379 + >
  380 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  381 + ng-value="attrType"
  382 + >
  383 + {{attrTypeValue | translate}}
  384 + </md-option>
  385 + </md-select>
  386 + <div ng-messages="theForm['opcAttributeType_' + serverIndex + mapIndex + attributeIndex].$error">
  387 + <div translate
  388 + ng-message="required"
  389 + >extension.required-type</div>
  390 + </div>
  391 + </md-input-container>
  392 + </section>
  393 +
  394 + <section flex layout="row">
  395 + <md-input-container flex="100" class="md-block">
  396 + <label translate>extension.value</label>
  397 + <input required name="opcAttributeValue_{{serverIndex}}{{mapIndex}}{{attributeIndex}}"
  398 + ng-model="attribute.value"
  399 + >
  400 + <div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
  401 + <div translate
  402 + ng-message="required"
  403 + >extension.opc-field-required</div>
  404 + </div>
  405 + </md-input-container>
  406 +
  407 + </section>
  408 +
  409 +
  410 + </md-card-content>
  411 + </md-card>
  412 + </li>
  413 + </ol>
  414 + </div>
  415 + <div flex layout="row" layout-align="start center">
  416 + <md-button class="md-primary md-raised"
  417 + ng-click="addNewAttribute(map.attributes)"
  418 + aria-label="{{ 'action.add' | translate }}"
  419 + >
  420 + <md-tooltip md-direction="top">
  421 + {{ 'extension.add-map' | translate }}
  422 + </md-tooltip>
  423 + <md-icon class="material-icons">add</md-icon>
  424 + <span translate>action.add</span>
  425 + </md-button>
  426 + </div>
  427 + </v-pane-content>
  428 + </v-pane>
  429 + </v-accordion>
  430 +
  431 + <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
  432 + <v-pane id="opc-timeseries-pane">
  433 + <v-pane-header>
  434 + {{ 'extension.opc-timeseries' | translate }}
  435 + </v-pane-header>
  436 + <v-pane-content>
  437 + <div ng-show="map.timeseries.length > 0">
  438 + <ol class="list-group">
  439 + <li class="list-group-item"
  440 + ng-repeat="(timeseriesIndex, timeseries) in map.timeseries"
  441 + >
  442 + <md-button aria-label="{{ 'action.remove' | translate }}"
  443 + class="md-icon-button"
  444 + ng-click="removeItem(timeseries, map.timeseries)"
  445 + >
  446 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  447 + <md-tooltip md-direction="top">
  448 + {{ 'action.remove' | translate }}
  449 + </md-tooltip>
  450 + </md-button>
  451 + <md-card>
  452 + <md-card-content>
  453 + <section flex layout="row">
  454 + <md-input-container flex="60" class="md-block">
  455 + <label translate>extension.key</label>
  456 + <input required
  457 + name="opcTimeseriesKey_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
  458 + ng-model="timeseries.key"
  459 + >
  460 + <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
  461 + <div translate
  462 + ng-message="required"
  463 + >extension.opc-field-required</div>
  464 + </div>
  465 + </md-input-container>
  466 + <md-input-container flex="40"
  467 + class="md-block tb-container-for-select"
  468 + >
  469 + <label translate>extension.type</label>
  470 + <md-select required
  471 + name="opcTimeseriesType_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
  472 + ng-model="timeseries.type"
  473 + >
  474 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  475 + ng-value="attrType"
  476 + >
  477 + {{attrTypeValue | translate}}
  478 + </md-option>
  479 + </md-select>
  480 + <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
  481 + <div translate
  482 + ng-message="required"
  483 + >extension.opc-field-required</div>
  484 + </div>
  485 + </md-input-container>
  486 + </section>
  487 + <section flex layout="row">
  488 + <md-input-container flex="100" class="md-block">
  489 + <label translate>extension.value</label>
  490 + <input required name="opcTimeseriesValue_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
  491 + <div ng-messages="theForm['opcTimeseriesValue_' + serverIndex + mapIndex + timeseriesIndex].$error">
  492 + <div translate ng-message="required">extension.required-value</div>
  493 + </div>
  494 + </md-input-container>
  495 + </section>
  496 + </md-card-content>
  497 + </md-card>
  498 + </li>
  499 + </ol>
  500 + </div>
  501 + <div flex layout="row" layout-align="start center">
  502 + <md-button class="md-primary md-raised"
  503 + ng-click="addNewAttribute(map.timeseries)"
  504 + aria-label="{{ 'action.add' | translate }}"
  505 + >
  506 + <md-tooltip md-direction="top">
  507 + {{ 'extension.add-timeseries' | translate }}
  508 + </md-tooltip>
  509 + <md-icon class="material-icons">add</md-icon>
  510 + <span translate>action.add</span>
  511 + </md-button>
  512 + </div>
  513 + </v-pane-content>
  514 + </v-pane>
  515 + </v-accordion>
  516 +
  517 +
  518 + </md-card-content>
  519 + </md-card>
  520 + </li>
  521 + </ol>
  522 + </div>
  523 + <div flex
  524 + layout="row"
  525 + layout-align="start center"
  526 + >
  527 + <md-button class="md-primary md-raised"
  528 + ng-click="addMap(server.mapping)"
  529 + aria-label="{{ 'action.add' | translate }}"
  530 + >
  531 + <md-tooltip md-direction="top">
  532 + {{ 'extension.add-map' | translate }}
  533 + </md-tooltip>
  534 + <md-icon class="material-icons">add</md-icon>
  535 + <span translate>action.add</span>
  536 + </md-button>
  537 + </div>
  538 + </v-pane-content>
  539 + </v-pane>
  540 + </v-accordion>
  541 +
  542 + </md-card-content>
  543 + </md-card>
  544 + </li>
  545 + </ol>
  546 +
  547 + <div flex
  548 + layout="row"
  549 + layout-align="start center"
  550 + >
  551 + <md-button class="md-primary md-raised"
  552 + ng-click="addServer(configuration.servers)"
  553 + aria-label="{{ 'action.add' | translate }}"
  554 + >
  555 + <md-icon class="material-icons">add</md-icon>
  556 + <span translate>extension.opc-add-another-server</span>
  557 + </md-button>
  558 + </div>
  559 +
  560 + </div>
  561 + </v-pane-content>
  562 + </v-pane>
  563 + </v-accordion>
  564 + <!--{{config}}-->
  565 + </md-card-content>
  566 +</md-card>
\ No newline at end of file
... ...
... ... @@ -37,4 +37,20 @@
37 37 .ace_text-input {
38 38 position:absolute!important
39 39 }
  40 +}
  41 +
  42 +.extensionDialog {
  43 + min-width: 1000px;
  44 +}
  45 +
  46 +.tb-container-for-select {
  47 + height: 58px;
  48 +}
  49 +
  50 +.tb-drop-file-input-hide {
  51 + height: 200%;
  52 + display: block;
  53 + position: absolute;
  54 + bottom: 0;
  55 + width: 100%;
40 56 }
\ No newline at end of file
... ...
... ... @@ -16,10 +16,12 @@
16 16
17 17 import ExtensionTableDirective from './extension-table.directive';
18 18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
  19 +import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
19 20 import {ParseToNull} from './extension-dialog.controller';
20 21
21 22 export default angular.module('thingsboard.extension', [])
22 23 .directive('tbExtensionTable', ExtensionTableDirective)
23 24 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
  25 + .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
24 26 .directive('parseToNull', ParseToNull)
25 27 .name;
\ No newline at end of file
... ...
... ... @@ -773,14 +773,44 @@ export default angular.module('thingsboard.locale', [])
773 773 "json-parse": "Unable to parse transformer json.",
774 774 "attributes": "Attributes",
775 775 "add-attribute": "Add attribute",
  776 + "add-map": "Add mapping element",
776 777 "timeseries": "Timeseries",
777 778 "add-timeseries": "Add timeseries",
  779 +
  780 + "opc-field-required": "Field is required",
  781 + "opc-server": "Servers",
  782 + "opc-add-server-hint": "Add server",
  783 + "opc-add-server-prompt": "Please add server",
  784 + "opc-server-id": "Server id",
  785 + "opc-timeseries": "Timeseries",
  786 + "opc-application-name": "Application name",
  787 + "opc-application-uri": "Application uri",
  788 + "opc-host": "Host",
  789 + "opc-port": "Port",
  790 + "opc-scan-period-in-seconds": "Scan period in seconds",
  791 + "opc-timeout-in-millis": "Timeout in milliseconds",
  792 + "opc-security": "Security",
  793 + "opc-identity": "Identity",
  794 + "opc-keystore": "Keystore",
  795 + "opc-type": "Type",
  796 + "opc-keystore-type":"Type",
  797 + "opc-keystore-location":"Location *",
  798 + "opc-keystore-password":"Password",
  799 + "opc-keystore-alias":"Alias",
  800 + "opc-keystore-key-password":"Key password",
  801 + "opc-mapping":"Mapping",
  802 + "opc-device-node-pattern":"Device node pattern",
  803 + "opc-device-name-pattern":"Device name pattern",
  804 + "opc-mapping-attributes":"Mapping attributes",
  805 + "opc-username":"Username",
  806 + "opc-password":"Password",
  807 + "opc-add-another-server":"Add another server",
778 808 },
779 809 "fullscreen": {
780 810 "expand": "Expand to fullscreen",
781 811 "exit": "Exit fullscreen",
782 812 "toggle": "Toggle fullscreen mode",
783   - "fullscreen": "Fullscreen"
  813 + "fullscreen": "Fullscreen",
784 814 },
785 815 "function": {
786 816 "function": "Function"
... ...