Commit 33451e059759920679baa5d0d4dc2b54d88c2ba0

Authored by Sergey Tarnavskiy
2 parents dad2a26f f00898c7

Add mqtt form

... ... @@ -30,6 +30,7 @@
30 30 "angular-material": "1.1.1",
31 31 "angular-material-data-table": "^0.10.9",
32 32 "angular-material-icons": "^0.7.1",
  33 + "angular-material-expansion-panel": "^0.7.2",
33 34 "angular-messages": "1.5.8",
34 35 "angular-route": "1.5.8",
35 36 "angular-sanitize": "1.5.8",
... ...
... ... @@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router';
39 39 import angularJwt from 'angular-jwt';
40 40 import 'angular-drag-and-drop-lists';
41 41 import mdDataTable from 'angular-material-data-table';
  42 +import 'angular-material-expansion-panel';
42 43 import ngTouch from 'angular-touch';
43 44 import 'angular-carousel';
44 45 import 'clipboard';
... ... @@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css';
82 83 import 'mdPickers/dist/mdPickers.min.css';
83 84 import 'angular-hotkeys/build/hotkeys.min.css';
84 85 import 'angular-carousel/dist/angular-carousel.min.css';
  86 +import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
85 87 import '../scss/main.scss';
86 88
87 89 import AppConfig from './app.config';
... ... @@ -103,6 +105,7 @@ angular.module('thingsboard', [
103 105 angularJwt,
104 106 'dndLists',
105 107 mdDataTable,
  108 + 'material.components.expansionPanels',
106 109 ngTouch,
107 110 'angular-carousel',
108 111 'ngclipboard',
... ...
... ... @@ -350,6 +350,20 @@ export default angular.module('thingsboard.types', [])
350 350 name: "extension.pem"
351 351 }
352 352 },
  353 + extensionOpcSecurityTypes: {
  354 + Basic128Rsa15: "Basic128Rsa15",
  355 + Basic256: "Basic256",
  356 + Basic256Sha256: "Basic256Sha256",
  357 + None: "None"
  358 + },
  359 + extensionIdentityType: {
  360 + anonymous: "extension.anonymous",
  361 + username: "extension.username"
  362 + },
  363 + extensionKeystoreType: {
  364 + PKCS12: "PKCS12",
  365 + JKS: "JKS"
  366 + },
353 367 latestTelemetry: {
354 368 value: "LATEST_TELEMETRY",
355 369 name: "attribute.scope-latest-telemetry",
... ...
... ... @@ -29,45 +29,88 @@ 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) {
  34 + vm.extension = angular.copy(extension);
  35 + editTransformers(vm.extension);
  36 + } else {
  37 + vm.extension = {};
39 38 }
40 39
41   - vm.cancel = cancel;
42   - vm.save = save;
43 40
  41 + vm.extensionTypeChange = function () {
  42 +
  43 + if (vm.extension.type === "HTTP") {
  44 + vm.extension.configuration = {
  45 + "converterConfigurations": []
  46 + };
  47 + }
  48 + if (vm.extension.type === "MQTT") {
  49 + vm.extension.configuration = {
  50 + "brokers": []
  51 + };
  52 + }
  53 + if (vm.extension.type === "OPC UA") {
  54 + vm.extension.configuration = {
  55 + "servers": []
  56 + };
  57 + }
  58 + };
  59 +
  60 + vm.cancel = cancel;
44 61 function cancel() {
45 62 $mdDialog.cancel();
46 63 }
  64 +
  65 + vm.save = save;
47 66 function save() {
48   - $mdDialog.hide();
49   - saveTransformers();
50   - if(vm.isAdd) {
51   - vm.allExtensions.push(vm.newExtension);
  67 + let $errorElement = angular.element('[name=theForm]').find('.ng-invalid');
  68 +
  69 + if ($errorElement.length) {
  70 +
  71 + let $mdDialogScroll = angular.element('md-dialog-content').scrollTop();
  72 + let $mdDialogTop = angular.element('md-dialog-content').offset().top;
  73 + let $errorElementTop = angular.element('[name=theForm]').find('.ng-invalid').eq(0).offset().top;
  74 +
  75 +
  76 + if ($errorElementTop !== $mdDialogTop) {
  77 + angular.element('md-dialog-content').animate({
  78 + scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 50
  79 + }, 500);
  80 + $errorElement.eq(0).focus();
  81 + }
52 82 } else {
53   - var index = vm.allExtensions.indexOf(extension);
54   - if(index > -1) {
55   - vm.allExtensions[index] = vm.newExtension;
  83 +
  84 + if(vm.isAdd) {
  85 + vm.allExtensions.push(vm.extension);
  86 + } else {
  87 + var index = vm.allExtensions.indexOf(extension);
  88 + if(index > -1) {
  89 + vm.allExtensions[index] = vm.extension;
  90 + }
56 91 }
57   - }
58 92
59   - var editedValue = angular.toJson(vm.allExtensions);
  93 + $mdDialog.hide();
  94 + saveTransformers();
60 95
61   - attributeService.saveEntityAttributes(vm.entityType, vm.entityId, types.attributesScope.shared.value, [{key:"configuration", value:editedValue}]).then(
62   - function success() {
63   - $scope.theForm.$setPristine();
64   - }
65   - );
  96 + var editedValue = angular.toJson(vm.allExtensions);
  97 +
  98 + attributeService
  99 + .saveEntityAttributes(
  100 + vm.entityType,
  101 + vm.entityId,
  102 + types.attributesScope.shared.value,
  103 + [{key:"configuration", value:editedValue}]
  104 + )
  105 + .then(function success() {
  106 + });
  107 +
  108 + }
66 109 }
67 110
68 111 vm.validateId = function() {
69 112 var coincidenceArray = vm.allExtensions.filter(function(ext) {
70   - return ext.id == vm.newExtension.id;
  113 + return ext.id == vm.extension.id;
71 114 });
72 115 if(coincidenceArray.length) {
73 116 if(!vm.isAdd) {
... ... @@ -82,11 +125,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
82 125 } else {
83 126 $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
84 127 }
85   - }
  128 + };
86 129
87 130 function saveTransformers() {
88   - if(vm.newExtension.type == types.extensionType.http) {
89   - var config = vm.newExtension.configuration.converterConfigurations;
  131 + if(vm.extension.type == types.extensionType.http) {
  132 + var config = vm.extension.configuration.converterConfigurations;
90 133 if(config && config.length > 0) {
91 134 for(let i=0;i<config.length;i++) {
92 135 for(let j=0;j<config[i].converters.length;j++){
... ... @@ -106,8 +149,8 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
106 149 }
107 150 }
108 151 }
109   - if(vm.newExtension.type == types.extensionType.mqtt) {
110   - var brokers = vm.newExtension.configuration.brokers;
  152 + if(vm.extension.type == types.extensionType.mqtt) {
  153 + var brokers = vm.extension.configuration.brokers;
111 154 if(brokers && brokers.length > 0) {
112 155 for(let i=0;i<brokers.length;i++) {
113 156 if(brokers[i].mapping && brokers[i].mapping.length > 0) {
... ... @@ -119,6 +162,27 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
119 162 delete brokers[i].mapping[j].converterType;
120 163 }
121 164 }
  165 + if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) {
  166 + for(let j=0;j<brokers[i].connectRequests.length;j++) {
  167 + delete brokers[i].connectRequests[j].nameExp;
  168 + }
  169 + }
  170 + if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) {
  171 + for(let j=0;j<brokers[i].disconnectRequests.length;j++) {
  172 + delete brokers[i].disconnectRequests[j].nameExp;
  173 + }
  174 + }
  175 + if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) {
  176 + for(let j=0;j<brokers[i].attributeRequests.length;j++) {
  177 + delete brokers[i].attributeRequests[j].nameExp;
  178 + }
  179 + for(let j=0;j<brokers[i].attributeRequests.length;j++) {
  180 + delete brokers[i].attributeRequests[j].attrKey;
  181 + }
  182 + for(let j=0;j<brokers[i].attributeRequests.length;j++) {
  183 + delete brokers[i].attributeRequests[j].requestId;
  184 + }
  185 + }
122 186 }
123 187 }
124 188 }
... ... @@ -174,6 +238,43 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
174 238 }
175 239 }
176 240 }
  241 + if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) {
  242 + for(let j=0;j<brokers[i].connectRequests.length;j++) {
  243 + if(brokers[i].connectRequests[j].deviceNameTopicExpression) {
  244 + brokers[i].connectRequests[j].nameExp = "deviceNameTopicExpression";
  245 + } else {
  246 + brokers[i].connectRequests[j].nameExp = "deviceNameJsonExpression";
  247 + }
  248 + }
  249 + }
  250 + if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) {
  251 + for(let j=0;j<brokers[i].disconnectRequests.length;j++) {
  252 + if(brokers[i].disconnectRequests[j].deviceNameTopicExpression) {
  253 + brokers[i].disconnectRequests[j].nameExp = "deviceNameTopicExpression";
  254 + } else {
  255 + brokers[i].disconnectRequests[j].nameExp = "deviceNameJsonExpression";
  256 + }
  257 + }
  258 + }
  259 + if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) {
  260 + for(let j=0;j<brokers[i].attributeRequests.length;j++) {
  261 + if(brokers[i].attributeRequests[j].deviceNameTopicExpression) {
  262 + brokers[i].attributeRequests[j].nameExp = "deviceNameTopicExpression";
  263 + } else {
  264 + brokers[i].attributeRequests[j].nameExp = "deviceNameJsonExpression";
  265 + }
  266 + if(brokers[i].attributeRequests[j].attributeKeyTopicExpression) {
  267 + brokers[i].attributeRequests[j].attrKey = "attributeKeyTopicExpression";
  268 + } else {
  269 + brokers[i].attributeRequests[j].attrKey = "attributeKeyJsonExpression";
  270 + }
  271 + if(brokers[i].attributeRequests[j].requestIdTopicExpression) {
  272 + brokers[i].attributeRequests[j].requestId = "requestIdTopicExpression";
  273 + } else {
  274 + brokers[i].attributeRequests[j].requestId = "requestIdJsonExpression";
  275 + }
  276 + }
  277 + }
177 278 }
178 279 }
179 280 }
... ...
... ... @@ -15,8 +15,8 @@
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;">
19   - <form name="theForm" ng-submit="vm.save()">
  18 +<md-dialog class="extensionDialog" aria-label="{{ (vm.isAdd ? 'extension.add' : 'extension.edit' ) | translate }}">
  19 + <form name="theForm" ng-submit="vm.save()" novalidate>
20 20 <md-toolbar>
21 21 <div class="md-toolbar-tools">
22 22 <h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2>
... ... @@ -26,49 +26,54 @@
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">
34 37 <fieldset ng-disabled="loading">
35 38 <section flex layout="row">
36   - <md-input-container flex="60" class="md-block">
  39 + <md-input-container flex="60" class="md-block" md-is-error="theForm.extensionId.$touched && theForm.extensionId.$invalid">
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   - <div translate ng-message="required">extension.id-required</div>
  43 + <div translate ng-message="required">extension.field-required</div>
41 44 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
42 45 </div>
43 46 </md-input-container>
44   - <md-input-container flex="40" class="md-block">
  47 +
  48 + <md-input-container flex="40" class="md-block" md-is-error="theForm.extensionType.$touched && theForm.extensionType.$invalid">
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   - <div translate ng-message="required">extension.type-required</div>
  58 + <div translate ng-message="required">extension.field-required</div>
53 59 </div>
54 60 </md-input-container>
55 61 </section>
56   -
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>
58   - <div tb-extension-form-mqtt config="vm.configuration" is-add="vm.isAdd" ng-if="vm.newExtension.type && vm.newExtension.type == vm.types.extensionType.mqtt"></div>
59   -
  62 + <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>
  63 + <div tb-extension-form-mqtt config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.mqtt"></div>
  64 + <div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
60 65 </fieldset>
61   -
62   - <!--<div>{{vm.newExtension}}</div>-->
63 66 </md-content>
64 67 </div>
65 68 </md-dialog-content>
  69 +
66 70 <md-dialog-actions layout="row">
67   - <span flex></span>
68   - <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
69   - class="md-raised md-primary">
  71 + <md-button type="submit"
  72 + class="md-raised md-primary"
  73 + >
70 74 {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
71 75 </md-button>
  76 +
72 77 <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}
73 78 </md-button>
74 79 </md-dialog-actions>
... ...
... ... @@ -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,
... ...
... ... @@ -53,74 +53,62 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
53 53 }
54 54 };
55 55
56   - if(scope.isAdd) {
57   - scope.converterConfigs = [];
58   - scope.config.converterConfigurations = scope.converterConfigs;
59   - } else {
60   - scope.converterConfigs = scope.config.converterConfigurations;
61   - }
62   -
63   - scope.updateValidity = function() {
64   - var valid = scope.converterConfigs && scope.converterConfigs.length > 0;
65   - scope.theForm.$setValidity('converterConfigs', valid);
66   - if(scope.converterConfigs.length) {
67   - for(let i=0;i<scope.converterConfigs.length;i++) {
68   - if(!scope.converterConfigs[i].converters.length) {
69   - scope.theForm.$setValidity('converters', false);
70   - break;
71   - } else {
72   - scope.theForm.$setValidity('converters', true);
73   - }
74   - }
75   - }
76   - };
77   -
78   - scope.$watch('converterConfigs', function() {
79   - scope.updateValidity();
80   - }, true);
81 56
82 57 scope.addConverterConfig = function() {
83 58 var newConverterConfig = {converterId:"", converters:[]};
84 59 scope.converterConfigs.push(newConverterConfig);
85   - }
  60 +
  61 + scope.converterConfigs[scope.converterConfigs.length - 1].converters = [];
  62 + scope.addConverter(scope.converterConfigs[scope.converterConfigs.length - 1].converters);
  63 + };
86 64
87 65 scope.removeConverterConfig = function(config) {
88 66 var index = scope.converterConfigs.indexOf(config);
89 67 if (index > -1) {
90 68 scope.converterConfigs.splice(index, 1);
91 69 }
92   - scope.theForm.$setDirty();
93   - }
  70 + };
94 71
95 72 scope.addConverter = function(converters) {
96   - var newConverter = {deviceNameJsonExpression:"", deviceTypeJsonExpression:"", attributes:[], timeseries:[]};
  73 + var newConverter = {
  74 + deviceNameJsonExpression:"",
  75 + deviceTypeJsonExpression:"",
  76 + attributes:[],
  77 + timeseries:[]
  78 + };
97 79 converters.push(newConverter);
98   - }
  80 + };
99 81
100 82 scope.removeConverter = function(converter, converters) {
101 83 var index = converters.indexOf(converter);
102 84 if (index > -1) {
103 85 converters.splice(index, 1);
104 86 }
105   - scope.theForm.$setDirty();
106   - }
  87 + };
107 88
108 89 scope.addAttribute = function(attributes) {
109 90 var newAttribute = {type:"", key:"", value:""};
110 91 attributes.push(newAttribute);
111   - }
  92 + };
112 93
113 94 scope.removeAttribute = function(attribute, attributes) {
114 95 var index = attributes.indexOf(attribute);
115 96 if (index > -1) {
116 97 attributes.splice(index, 1);
117 98 }
118   - scope.theForm.$setDirty();
  99 + };
  100 +
  101 +
  102 + if(scope.isAdd) {
  103 + scope.converterConfigs = scope.config.converterConfigurations;
  104 + scope.addConverterConfig();
  105 + } else {
  106 + scope.converterConfigs = scope.config.converterConfigurations;
119 107 }
120 108
121 109 scope.transformerTypeChange = function(attribute) {
122 110 attribute.transformer = "";
123   - }
  111 + };
124 112
125 113 scope.validateTransformer = function (model, editorName) {
126 114 if(model && model.length) {
... ... @@ -131,10 +119,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
131 119 scope.theForm[editorName].$setValidity('transformerJSON', false);
132 120 }
133 121 }
134   - }
135   -
  122 + };
  123 +
136 124 $compile(element.contents())(scope);
137   - }
  125 + };
138 126
139 127 return {
140 128 restrict: "A",
... ...
... ... @@ -23,18 +23,19 @@
23 23 </md-card-title>
24 24 <md-card-content>
25 25 <v-accordion id="http-converter-configs-accordion" class="vAccordion--default">
26   - <v-pane id="http-converters-pane" expanded="isAdd">
  26 + <v-pane id="http-converters-pane" expanded="true">
27 27 <v-pane-header>
28 28 {{ 'extension.converter-configurations' | translate }}
29 29 </v-pane-header>
30 30 <v-pane-content>
31   - <div ng-if="converterConfigs.length === 0">
32   - <span translate layout-align="center center" class="tb-prompt">extension.add-config-prompt</span>
33   - </div>
34 31 <div ng-if="converterConfigs.length > 0">
35 32 <ol class="list-group">
36   - <li class="list-group-item" ng-repeat="(configIndex,config) in converterConfigs">
37   - <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeConverterConfig(config)">
  33 + <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs">
  34 + <md-button aria-label="{{ 'action.remove' | translate }}"
  35 + class="md-icon-button"
  36 + ng-click="removeConverterConfig(config)"
  37 + ng-hide="converterConfigs.length < 2"
  38 + >
38 39 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
39 40 <md-tooltip md-direction="top">
40 41 {{ 'action.remove' | translate }}
... ... @@ -43,11 +44,11 @@
43 44 <md-card>
44 45 <md-card-content>
45 46
46   - <md-input-container class="md-block">
  47 + <md-input-container class="md-block" md-is-error="theForm['httpConverterId_' + configIndex].$touched && theForm['httpConverterId_' + configIndex].$invalid">
47 48 <label translate>extension.converter-id</label>
48 49 <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
49 50 <div ng-messages="theForm['httpConverterId_' + configIndex].$error">
50   - <div translate ng-message="required">extension.converter-id-required</div>
  51 + <div translate ng-message="required">extension.field-required</div>
51 52 </div>
52 53 </md-input-container>
53 54 <md-input-container class="md-block">
... ... @@ -55,18 +56,21 @@
55 56 <input name="httpToken" ng-model="config.token" parse-to-null>
56 57 </md-input-container>
57 58 <v-accordion id="http-converters-accordion" class="vAccordion--default">
58   - <v-pane id="http-converters-pane">
  59 + <v-pane id="http-converters-pane" expanded="true">
59 60 <v-pane-header>
60 61 {{ 'extension.converters' | translate }}
61 62 </v-pane-header>
62 63 <v-pane-content>
63   - <div ng-if="config.converters.length === 0">
64   - <span translate layout-align="center center" class="tb-prompt">extension.add-converter-prompt</span>
65   - </div>
66 64 <div ng-if="config.converters.length > 0">
67 65 <ol class="list-group">
68   - <li class="list-group-item" ng-repeat="(converterIndex,converter) in config.converters">
69   - <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeConverter(converter, config.converters)">
  66 + <li class="list-group-item"
  67 + ng-repeat="(converterIndex,converter) in config.converters"
  68 + >
  69 + <md-button aria-label="{{ 'action.remove' | translate }}"
  70 + class="md-icon-button"
  71 + ng-click="removeConverter(converter, config.converters)"
  72 + ng-hide="config.converters.length < 2"
  73 + >
70 74 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
71 75 <md-tooltip md-direction="top">
72 76 {{ 'action.remove' | translate }}
... ... @@ -74,18 +78,18 @@
74 78 </md-button>
75 79 <md-card>
76 80 <md-card-content>
77   - <md-input-container class="md-block">
  81 + <md-input-container class="md-block" md-is-error="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceNameExp_' + configIndex + converterIndex].$invalid">
78 82 <label translate>extension.device-name-expression</label>
79 83 <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
80 84 <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error">
81   - <div translate ng-message="required">extension.device-name-expression-required</div>
  85 + <div translate ng-message="required">extension.field-required</div>
82 86 </div>
83 87 </md-input-container>
84   - <md-input-container class="md-block">
  88 + <md-input-container class="md-block" md-is-error="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$invalid">
85 89 <label translate>extension.device-type-expression</label>
86 90 <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
87 91 <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error">
88   - <div translate ng-message="required">extension.device-type-expression-required</div>
  92 + <div translate ng-message="required">extension.field-required</div>
89 93 </div>
90 94 </md-input-container>
91 95
... ... @@ -107,14 +111,14 @@
107 111 <md-card>
108 112 <md-card-content>
109 113 <section flex layout="row">
110   - <md-input-container flex="60" class="md-block">
  114 + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$invalid">
111 115 <label translate>extension.key</label>
112 116 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
113 117 <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error">
114   - <div translate ng-message="required">extension.required-key</div>
  118 + <div translate ng-message="required">extension.field-required</div>
115 119 </div>
116 120 </md-input-container>
117   - <md-input-container flex="40" class="md-block">
  121 + <md-input-container flex="40" class="md-block" md-is-error="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$invalid">
118 122 <label translate>extension.type</label>
119 123 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
120 124 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
... ... @@ -122,16 +126,16 @@
122 126 </md-option>
123 127 </md-select>
124 128 <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error">
125   - <div translate ng-message="required">extension.required-type</div>
  129 + <div translate ng-message="required">extension.field-required</div>
126 130 </div>
127 131 </md-input-container>
128 132 </section>
129 133 <section flex layout="row">
130   - <md-input-container flex="60" class="md-block">
  134 + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$invalid">
131 135 <label translate>extension.value</label>
132 136 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
133 137 <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error">
134   - <div translate ng-message="required">extension.required-value</div>
  138 + <div translate ng-message="required">extension.field-required</div>
135 139 </div>
136 140 </md-input-container>
137 141
... ... @@ -172,11 +176,8 @@
172 176 <div flex layout="row" layout-align="start center">
173 177 <md-button class="md-primary md-raised"
174 178 ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}">
175   - <md-tooltip md-direction="top">
176   - {{ 'extension.add-attribute' | translate }}
177   - </md-tooltip>
178 179 <md-icon class="material-icons">add</md-icon>
179   - <span translate>action.add</span>
  180 + <span translate>extension.add-attribute</span>
180 181 </md-button>
181 182 </div>
182 183 </v-pane-content>
... ... @@ -202,14 +203,14 @@
202 203 <md-card>
203 204 <md-card-content>
204 205 <section flex layout="row">
205   - <md-input-container flex="60" class="md-block">
  206 + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$invalid">
206 207 <label translate>extension.key</label>
207 208 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
208 209 <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error">
209   - <div translate ng-message="required">extension.required-key</div>
  210 + <div translate ng-message="required">extension.field-required</div>
210 211 </div>
211 212 </md-input-container>
212   - <md-input-container flex="40" class="md-block">
  213 + <md-input-container flex="40" class="md-block" md-is-error="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$invalid">
213 214 <label translate>extension.type</label>
214 215 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
215 216 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
... ... @@ -217,16 +218,16 @@
217 218 </md-option>
218 219 </md-select>
219 220 <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error">
220   - <div translate ng-message="required">extension.required-type</div>
  221 + <div translate ng-message="required">extension.field-required</div>
221 222 </div>
222 223 </md-input-container>
223 224 </section>
224 225 <section flex layout="row">
225   - <md-input-container flex="60" class="md-block">
  226 + <md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$invalid">
226 227 <label translate>extension.value</label>
227 228 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
228 229 <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error">
229   - <div translate ng-message="required">extension.required-value</div>
  230 + <div translate ng-message="required">extension.field-required</div>
230 231 </div>
231 232 </md-input-container>
232 233
... ... @@ -267,11 +268,8 @@
267 268 <div flex layout="row" layout-align="start center">
268 269 <md-button class="md-primary md-raised"
269 270 ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
270   - <md-tooltip md-direction="top">
271   - {{ 'extension.add-timeseries' | translate }}
272   - </md-tooltip>
273 271 <md-icon class="material-icons">add</md-icon>
274   - <span translate>action.add</span>
  272 + <span translate>extension.add-timeseries</span>
275 273 </md-button>
276 274 </div>
277 275 </v-pane-content>
... ... @@ -285,11 +283,8 @@
285 283 <div flex layout="row" layout-align="start center">
286 284 <md-button class="md-primary md-raised"
287 285 ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}">
288   - <md-tooltip md-direction="top">
289   - {{ 'extension.add-converter' | translate }}
290   - </md-tooltip>
291 286 <md-icon class="material-icons">add</md-icon>
292   - <span translate>action.add</span>
  287 + <span translate>extension.add-converter</span>
293 288 </md-button>
294 289 </div>
295 290 </v-pane-content>
... ... @@ -304,11 +299,8 @@
304 299 <div flex layout="row" layout-align="start center">
305 300 <md-button class="md-primary md-raised"
306 301 ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
307   - <md-tooltip md-direction="top">
308   - {{ 'extension.add-config' | translate }}
309   - </md-tooltip>
310 302 <md-icon class="material-icons">add</md-icon>
311   - <span translate>action.add</span>
  303 + <span translate>extension.add-config</span>
312 304 </md-button>
313 305 </div>
314 306 </v-pane-content>
... ...
... ... @@ -33,14 +33,22 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
33 33 scope.types = types;
34 34 scope.theForm = scope.$parent.theForm;
35 35
36   - scope.nameExpressions = {
  36 + scope.deviceNameExpressions = {
37 37 deviceNameJsonExpression: "extension.converter-json",
38 38 deviceNameTopicExpression: "extension.topic"
39 39 };
40   - scope.typeExpressions = {
  40 + scope.deviceTypeExpressions = {
41 41 deviceTypeJsonExpression: "extension.converter-json",
42 42 deviceTypeTopicExpression: "extension.topic"
43 43 };
  44 + scope.attributeKeyExpressions = {
  45 + attributeKeyJsonExpression: "extension.converter-json",
  46 + attributeKeyTopicExpression: "extension.topic"
  47 + };
  48 + scope.requestIdExpressions = {
  49 + requestIdJsonExpression: "extension.converter-json",
  50 + requestIdTopicExpression: "extension.topic"
  51 + }
44 52
45 53 scope.extensionCustomConverterOptions = {
46 54 useWrapMode: false,
... ... @@ -58,17 +66,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
58 66 }
59 67 };
60 68
61   -
62   - if(scope.isAdd) {
63   - scope.brokers = [];
64   - scope.config.brokers = scope.brokers;
65   - } else {
66   - scope.brokers = scope.config.brokers;
67   - }
68   -
69 69 scope.updateValidity = function () {
70   - var valid = scope.brokers && scope.brokers.length > 0;
71   - scope.theForm.$setValidity('brokers', valid);
72 70 if(scope.brokers.length) {
73 71 for(let i=0;i<scope.brokers.length;i++) {
74 72 if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) {
... ... @@ -81,57 +79,116 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
81 79 }
82 80 }
83 81 }
84   - }
  82 + };
85 83
86 84 scope.$watch('brokers', function() {
87 85 scope.updateValidity();
88 86 }, true);
89 87
90 88 scope.addBroker = function() {
91   - var newBroker = {host:"localhost", port:1882, ssl:false, retryInterval:3000, credentials:{type:"anonymous"}, mapping:[]};
  89 + var newBroker = {
  90 + host: "localhost",
  91 + port: 1882,
  92 + ssl: false,
  93 + retryInterval: 3000,
  94 + credentials: {type:"anonymous"},
  95 + mapping: [],
  96 + connectRequests: [],
  97 + disconnectRequests: [],
  98 + attributeRequests: [],
  99 + attributeUpdates: [],
  100 + serverSideRpc: []
  101 + };
92 102 scope.brokers.push(newBroker);
93   - }
  103 + };
94 104
95 105 scope.removeBroker = function(broker) {
96 106 var index = scope.brokers.indexOf(broker);
97 107 if (index > -1) {
98 108 scope.brokers.splice(index, 1);
99 109 }
100   - scope.theForm.$setDirty();
  110 + };
  111 +
  112 + if(scope.isAdd) {
  113 + scope.brokers = [];
  114 + scope.config.brokers = scope.brokers;
  115 + scope.addBroker();
  116 + } else {
  117 + scope.brokers = scope.config.brokers;
101 118 }
102 119
103 120 scope.addMap = function(mapping) {
104 121 var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}};
105 122
106 123 mapping.push(newMap);
107   - }
  124 + };
108 125
109 126 scope.removeMap = function(map, mapping) {
110 127 var index = mapping.indexOf(map);
111 128 if (index > -1) {
112 129 mapping.splice(index, 1);
113 130 }
114   - scope.theForm.$setDirty();
115   - }
  131 + };
116 132
117 133 scope.addAttribute = function(attributes) {
118 134 var newAttribute = {type:"", key:"", value:""};
119 135 attributes.push(newAttribute);
120   - }
  136 + };
121 137
122 138 scope.removeAttribute = function(attribute, attributes) {
123 139 var index = attributes.indexOf(attribute);
124 140 if (index > -1) {
125 141 attributes.splice(index, 1);
126 142 }
127   - scope.theForm.$setDirty();
128   - }
  143 + };
  144 +
  145 + scope.addConnectRequest = function(requests, type) {
  146 + var newRequest = {};
  147 + if(type == "connect") {
  148 + newRequest.topicFilter = "sensors/connect";
  149 + } else {
  150 + newRequest.topicFilter = "sensors/disconnect";
  151 + }
  152 + requests.push(newRequest);
  153 + };
  154 +
  155 + scope.addAttributeRequest = function(requests) {
  156 + var newRequest = {
  157 + topicFilter: "sensors/attributes",
  158 + clientScope: false,
  159 + responseTopicExpression: "sensors/${deviceName}/attributes/${responseId}",
  160 + valueExpression: "${attributeValue}"
  161 + };
  162 + requests.push(newRequest);
  163 + };
  164 +
  165 + scope.addAttributeUpdate = function(updates) {
  166 + var newUpdate = {
  167 + deviceNameFilter: ".*",
  168 + attributeFilter: ".*",
  169 + topicExpression: "sensor/${deviceName}/${attributeKey}",
  170 + valueExpression: "{\"${attributeKey}\":\"${attributeValue}\"}"
  171 + }
  172 + updates.push(newUpdate);
  173 + };
  174 +
  175 + scope.addServerSideRpc = function(rpcRequests) {
  176 + var newRpc = {
  177 + deviceNameFilter: ".*",
  178 + methodFilter: "echo",
  179 + requestTopicExpression: "sensor/${deviceName}/request/${methodName}/${requestId}",
  180 + responseTopicExpression: "sensor/${deviceName}/response/${methodName}/${requestId}",
  181 + responseTimeout: 10000,
  182 + valueExpression: "${params}"
  183 + };
  184 + rpcRequests.push(newRpc);
  185 + };
129 186
130 187 scope.changeCredentials = function(broker) {
131 188 var type = broker.credentials.type;
132 189 broker.credentials = {};
133 190 broker.credentials.type = type;
134   - }
  191 + };
135 192
136 193 scope.changeConverterType = function(map) {
137 194 if(map.converterType == "custom"){
... ... @@ -140,20 +197,32 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
140 197 if(map.converterType == "json") {
141 198 map.converter = {attributes:[],timeseries:[]};
142 199 }
143   - }
  200 + };
144 201
145   - scope.changeNameExpression = function(converter) {
146   - if(converter.nameExp == "deviceNameJsonExpression") {
147   - if(converter.deviceNameTopicExpression) {
148   - delete converter.deviceNameTopicExpression;
  202 + scope.changeNameExpression = function(element, type) {
  203 + if(element.nameExp == "deviceNameJsonExpression") {
  204 + if(element.deviceNameTopicExpression) {
  205 + delete element.deviceNameTopicExpression;
  206 + }
  207 + if(type) {
  208 + element.deviceNameJsonExpression = "${$.serialNumber}";
149 209 }
150 210 }
151   - if(converter.nameExp == "deviceNameTopicExpression") {
152   - if(converter.deviceNameJsonExpression) {
153   - delete converter.deviceNameJsonExpression;
  211 + if(element.nameExp == "deviceNameTopicExpression") {
  212 + if(element.deviceNameJsonExpression) {
  213 + delete element.deviceNameJsonExpression;
  214 + }
  215 + if(type && type == "connect") {
  216 + element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/connect)";
  217 + }
  218 + if(type && type == "disconnect") {
  219 + element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/disconnect)";
  220 + }
  221 + if(type && type == "attribute") {
  222 + element.deviceNameTopicExpression = "(?<=sensors\\/)(.*?)(?=\\/attributes)";
154 223 }
155 224 }
156   - }
  225 + };
157 226
158 227 scope.changeTypeExpression = function(converter) {
159 228 if(converter.typeExp == "deviceTypeJsonExpression") {
... ... @@ -166,7 +235,37 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
166 235 delete converter.deviceTypeJsonExpression;
167 236 }
168 237 }
169   - }
  238 + };
  239 +
  240 + scope.changeAttrKeyExpression = function(request) {
  241 + if(request.attrKey == "attributeKeyJsonExpression") {
  242 + if(request.attributeKeyTopicExpression) {
  243 + delete request.attributeKeyTopicExpression;
  244 + }
  245 + request.attributeKeyJsonExpression = "${$.key}";
  246 + }
  247 + if(request.attrKey == "attributeKeyTopicExpression") {
  248 + if(request.attributeKeyJsonExpression) {
  249 + delete request.attributeKeyJsonExpression;
  250 + }
  251 + request.attributeKeyTopicExpression = "(?<=attributes\\/)(.*?)(?=\\/request)";
  252 + }
  253 + };
  254 +
  255 + scope.changeRequestIdExpression = function(request) {
  256 + if(request.requestId == "requestIdJsonExpression") {
  257 + if(request.requestIdTopicExpression) {
  258 + delete request.requestIdTopicExpression;
  259 + }
  260 + request.requestIdJsonExpression = "${$.requestId}";
  261 + }
  262 + if(request.requestId == "requestIdTopicExpression") {
  263 + if(request.requestIdJsonExpression) {
  264 + delete request.requestIdJsonExpression;
  265 + }
  266 + request.requestIdTopicExpression = "(?<=request\\/)(.*?)($)";
  267 + }
  268 + };
170 269
171 270 scope.validateCustomConverter = function(model, editorName) {
172 271 if(model && model.length) {
... ... @@ -177,7 +276,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
177 276 scope.theForm[editorName].$setValidity('converterJSON', false);
178 277 }
179 278 }
180   - }
  279 + };
181 280
182 281 scope.fileAdded = function($file, broker, fileType) {
183 282 var reader = new FileReader();
... ... @@ -204,7 +303,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
204 303 });
205 304 };
206 305 reader.readAsDataURL($file.file);
207   - }
  306 + };
208 307
209 308 scope.clearFile = function(broker, fileType) {
210 309 scope.theForm.$setDirty();
... ... @@ -220,10 +319,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
220 319 broker.credentials.certFileName = null;
221 320 broker.credentials.cert = null;
222 321 }
223   - }
  322 + };
224 323
225 324 $compile(element.contents())(scope);
226   - }
  325 + };
227 326
228 327 return {
229 328 restrict: "A",
... ...
... ... @@ -23,18 +23,15 @@
23 23 </md-card-title>
24 24 <md-card-content>
25 25 <v-accordion id="mqtt-brokers-accordion" class="vAccordion--default">
26   - <v-pane id="mqtt-brokers-pane" expanded="isAdd">
  26 + <v-pane id="mqtt-brokers-pane" expanded="true">
27 27 <v-pane-header>
28 28 {{ 'extension.brokers' | translate }}
29 29 </v-pane-header>
30 30 <v-pane-content>
31   - <div ng-if="brokers.length === 0">
32   - <span translate layout-align="center center" class="tb-prompt">extension.add-broker-prompt</span>
33   - </div>
34 31 <div ng-if="brokers.length > 0">
35 32 <ol class="list-group">
36 33 <li class="list-group-item" ng-repeat="(brokerIndex,broker) in brokers">
37   - <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeBroker(broker)">
  34 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeBroker(broker)" ng-hide="brokers.length < 2">
38 35 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
39 36 <md-tooltip md-direction="top">
40 37 {{ 'action.remove' | translate }}
... ... @@ -47,7 +44,7 @@
47 44 <label translate>extension.port</label>
48 45 <input required type="number" min="1" max="65535" name="mqttPort_{{brokerIndex}}" ng-model="broker.port">
49 46 <div ng-messages="theForm['mqttPort_' + brokerIndex].$error">
50   - <div translate ng-message="required">extension.port-required</div>
  47 + <div translate ng-message="required">extension.field-required</div>
51 48 <div translate ng-message="min">extension.port-range</div>
52 49 <div translate ng-message="max">extension.port-range</div>
53 50 </div>
... ... @@ -56,7 +53,7 @@
56 53 <label translate>extension.host</label>
57 54 <input required name="mqttHost_{{brokerIndex}}" ng-model="broker.host">
58 55 <div ng-messages="theForm['mqttHost_' + brokerIndex].$error">
59   - <div translate ng-message="required">extension.host-required</div>
  56 + <div translate ng-message="required">extension.field-required</div>
60 57 </div>
61 58 </md-input-container>
62 59 </section>
... ... @@ -65,7 +62,7 @@
65 62 <label translate>extension.retry-interval</label>
66 63 <input required type="number" name="mqttRetryInterval_{{brokerIndex}}" ng-model="broker.retryInterval">
67 64 <div ng-messages="theForm['mqttRetryInterval_' + brokerIndex].$error">
68   - <div translate ng-message="required">extension.retry-interval-required</div>
  65 + <div translate ng-message="required">extension.field-required</div>
69 66 </div>
70 67 </md-input-container>
71 68 <md-input-container flex="50" class="md-block">
... ... @@ -76,30 +73,30 @@
76 73 </md-option>
77 74 </md-select>
78 75 </md-input-container>
79   - <md-input-container flex="10" class="md-block t-right">
  76 + <md-input-container flex="10" class="md-block">
80 77 <md-checkbox flex aria-label="{{ 'extension.ssl' | translate }}"
81 78 ng-model="broker.ssl">{{ 'extension.ssl' | translate }}
82 79 </md-checkbox>
83 80 </md-input-container>
84 81 </section>
85 82 <section flex layout="row" ng-if='broker.credentials.type == "basic"'>
86   - <md-input-container flex="40" class="md-block">
  83 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttUsername_' + brokerIndex].$touched && theForm['mqttUsername_' + brokerIndex].$invalid">
87 84 <label translate>extension.username</label>
88 85 <input required name="mqttUsername_{{brokerIndex}}" ng-model="broker.credentials.username">
89 86 <div ng-messages="theForm['mqttUsername_' + brokerIndex].$error">
90   - <div translate ng-message="required">extension.username-required</div>
  87 + <div translate ng-message="required">extension.field-required</div>
91 88 </div>
92 89 </md-input-container>
93   - <md-input-container flex="60" class="md-block">
  90 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttPassword_' + brokerIndex].$touched && theForm['mqttPassword_' + brokerIndex].$invalid">
94 91 <label translate>extension.password</label>
95 92 <input required name="mqttPassword_{{brokerIndex}}" ng-model="broker.credentials.password">
96 93 <div ng-messages="theForm['mqttPassword_' + brokerIndex].$error">
97   - <div translate ng-message="required">extension.password-required</div>
  94 + <div translate ng-message="required">extension.field-required</div>
98 95 </div>
99 96 </md-input-container>
100 97 </section>
101   - <section flex layout="column" ng-if='broker.credentials.type == "cert.PEM"'>
102   - <div class="tb-container">
  98 + <section flex layout="column" ng-if='broker.credentials.type == "cert.PEM"' class="dropdown-section">
  99 + <div class="tb-container" ng-class="broker.credentials.caCertFileName ? 'ng-valid' : 'ng-invalid'">
103 100 <label class="tb-label" translate>extension.ca-cert</label>
104 101 <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "caCert")' class="tb-file-select-container">
105 102 <div class="tb-file-clear-container">
... ... @@ -120,7 +117,7 @@
120 117 <div ng-if="!broker.credentials.caCertFileName" class="tb-error-message" translate>extension.no-file</div>
121 118 <div ng-if="broker.credentials.caCertFileName">{{broker.credentials.caCertFileName}}</div>
122 119 </div>
123   - <div class="tb-container">
  120 + <div class="tb-container" ng-class="broker.credentials.privateKeyFileName ? 'ng-valid' : 'ng-invalid'">
124 121 <label class="tb-label" translate>extension.private-key</label>
125 122 <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "privateKey")' class="tb-file-select-container">
126 123 <div class="tb-file-clear-container">
... ... @@ -182,7 +179,7 @@
182 179 <md-card>
183 180 <md-card-content>
184 181 <section flex layout="row">
185   - <md-input-container flex="40" class="md-block">
  182 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttConverterType_' + brokerIndex + mapIndex].$touched && theForm['mqttConverterType_' + brokerIndex + mapIndex].$invalid">
186 183 <label translate>extension.converter-type</label>
187 184 <md-select required name="mqttConverterType_{{brokerIndex}}{{mapIndex}}" ng-model="map.converterType" ng-change="changeConverterType(map)">
188 185 <md-option ng-repeat="(converterType, value) in types.mqttConverterTypes" ng-value="converterType">
... ... @@ -190,64 +187,70 @@
190 187 </md-option>
191 188 </md-select>
192 189 <div ng-messages="theForm['mqttConverterType_' + brokerIndex + mapIndex].$error">
193   - <div translate ng-message="required">extension.converter-type-required</div>
  190 + <div translate ng-message="required">extension.field-required</div>
194 191 </div>
195 192 </md-input-container>
196 193 <md-input-container flex="60" class="md-block">
197 194 <label translate>extension.topic-filter</label>
198 195 <input required name="mqttTopicFilter_{{brokerIndex}}{{mapIndex}}" ng-model="map.topicFilter">
199 196 <div ng-messages="theForm['mqttTopicFilter_' + brokerIndex + mapIndex].$error">
200   - <div translate ng-message="required">extension.topic-filter-required</div>
  197 + <div translate ng-message="required">extension.field-required</div>
201 198 </div>
202 199 </md-input-container>
203 200 </section>
204 201
205 202 <div ng-if='map.converterType =="json"' ng-init="map.converter.type = 'json'">
206 203 <section flex layout="row">
207   - <md-input-container flex="40" class="md-block">
208   - <label translate>extension.name-expression</label>
  204 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$touched && theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$invalid">
  205 + <label translate>extension.device-name-expression</label>
209 206 <md-select required name="mqttDeviceNameExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.nameExp" ng-change="changeNameExpression(map.converter)">
210   - <md-option ng-repeat="(key, value) in nameExpressions" ng-value='key'>
  207 + <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
211 208 {{value | translate}}
212 209 </md-option>
213 210 </md-select>
  211 + <div ng-messages="theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$error">
  212 + <div translate ng-message="required">extension.field-required</div>
  213 + </div>
214 214 </md-input-container>
215   - <md-input-container ng-if="map.converter.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
  215 + <md-input-container ng-if="map.converter.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block" md-is-error="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$touched && theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$invalid">
216 216 <label translate>extension.json-name-expression</label>
217 217 <input required name="mqttJsonNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameJsonExpression">
218 218 <div ng-messages="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$error">
219   - <div translate ng-message="required">extension.json-name-expression-required</div>
  219 + <div translate ng-message="required">extension.field-required</div>
220 220 </div>
221 221 </md-input-container>
222   - <md-input-container ng-if="map.converter.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
  222 + <md-input-container ng-if="map.converter.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block" md-is-error="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$touched && theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$invalid">
223 223 <label translate>extension.topic-name-expression</label>
224 224 <input required name="mqttTopicNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameTopicExpression">
225 225 <div ng-messages="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$error">
226   - <div translate ng-message="required">extension.topic-name-expression-required</div>
  226 + <div translate ng-message="required">extension.field-required</div>
227 227 </div>
228 228 </md-input-container>
229 229 </section>
230 230 <section flex layout="row">
231   - <md-input-container flex="40" class="md-block">
232   - <label translate>extension.type-expression</label>
  231 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$touched && theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$invalid">
  232 + <label translate>extension.device-type-expression</label>
233 233 <md-select required name="mqttDeviceTypeExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.typeExp" ng-change="changeTypeExpression(map.converter)">
234   - <md-option ng-repeat="(key, value) in typeExpressions" ng-value='key'>
  234 + <md-option ng-repeat="(key, value) in deviceTypeExpressions" ng-value='key'>
235 235 {{value | translate}}
236 236 </md-option>
237 237 </md-select>
  238 + <div ng-messages="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$error">
  239 + <div translate ng-message="required">extension.field-required</div>
  240 + </div>
238 241 </md-input-container>
239   - <md-input-container ng-if="map.converter.typeExp == 'deviceTypeJsonExpression'" flex="60" class="md-block">
  242 + <md-input-container ng-if="map.converter.typeExp == 'deviceTypeJsonExpression'" flex="60" class="md-block" md-is-error="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$touched && theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$invalid">
240 243 <label translate>extension.json-type-expression</label>
241 244 <input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression">
242 245 <div ng-messages="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$error">
243   - <div translate ng-message="required">extension.json-type-expression-required</div>
  246 + <div translate ng-message="required">extension.field-required</div>
244 247 </div>
245 248 </md-input-container>
246   - <md-input-container ng-if="map.converter.typeExp == 'deviceTypeTopicExpression'" flex="60" class="md-block">
  249 + <md-input-container ng-if="map.converter.typeExp == 'deviceTypeTopicExpression'" flex="60" class="md-block" md-is-error="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$touched && theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$invalid">
247 250 <label translate>extension.topic-type-expression</label>
248 251 <input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression">
249 252 <div ng-messages="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$error">
250   - <div translate ng-message="required">extension.topic-type-expression-required</div>
  253 + <div translate ng-message="required">extension.field-required</div>
251 254 </div>
252 255 </md-input-container>
253 256 </section>
... ... @@ -256,11 +259,11 @@
256 259 <label translate>extension.timeout</label>
257 260 <input type="number" name="mqttTimeout_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.timeout" parse-to-null>
258 261 </md-input-container>
259   - <md-input-container flex="60" class="md-block">
  262 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$touched && theForm['mqttFilterExpression' + brokerIndex + mapIndex].$invalid">
260 263 <label translate>extension.filter-expression</label>
261 264 <input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
262 265 <div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error">
263   - <div translate ng-message="required">extension.filter-expression-required</div>
  266 + <div translate ng-message="required">extension.field-required</div>
264 267 </div>
265 268 </md-input-container>
266 269 </section>
... ... @@ -301,14 +304,14 @@
301 304 <md-card>
302 305 <md-card-content>
303 306 <section flex layout="row">
304   - <md-input-container flex="60" class="md-block">
  307 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$invalid">
305 308 <label translate>extension.key</label>
306 309 <input required name="mqttAttributeKey_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.key">
307 310 <div ng-messages="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$error">
308   - <div translate ng-message="required">extension.required-key</div>
  311 + <div translate ng-message="required">extension.field-required</div>
309 312 </div>
310 313 </md-input-container>
311   - <md-input-container flex="40" class="md-block">
  314 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$invalid">
312 315 <label translate>extension.type</label>
313 316 <md-select required name="mqttAttributeType_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.type">
314 317 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
... ... @@ -316,15 +319,15 @@
316 319 </md-option>
317 320 </md-select>
318 321 <div ng-messages="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$error">
319   - <div translate ng-message="required">extension.required-type</div>
  322 + <div translate ng-message="required">extension.field-required</div>
320 323 </div>
321 324 </md-input-container>
322 325 </section>
323   - <md-input-container class="md-block">
  326 + <md-input-container class="md-block" md-is-error="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$invalid">
324 327 <label translate>extension.value</label>
325 328 <input required name="mqttAttributeValue_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.value">
326 329 <div ng-messages="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$error">
327   - <div translate ng-message="required">extension.required-value</div>
  330 + <div translate ng-message="required">extension.field-required</div>
328 331 </div>
329 332 </md-input-container>
330 333 </md-card-content>
... ... @@ -335,11 +338,8 @@
335 338 <div flex layout="row" layout-align="start center">
336 339 <md-button class="md-primary md-raised"
337 340 ng-click="addAttribute(map.converter.attributes)" aria-label="{{ 'action.add' | translate }}">
338   - <md-tooltip md-direction="top">
339   - {{ 'extension.add-attribute' | translate }}
340   - </md-tooltip>
341 341 <md-icon class="material-icons">add</md-icon>
342   - <span translate>action.add</span>
  342 + <span translate>extension.add-attribute</span>
343 343 </md-button>
344 344 </div>
345 345 </v-pane-content>
... ... @@ -364,14 +364,14 @@
364 364 <md-card>
365 365 <md-card-content>
366 366 <section flex layout="row">
367   - <md-input-container flex="60" class="md-block">
  367 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$invalid">
368 368 <label translate>extension.key</label>
369 369 <input required name="mqttTimeseriesKey_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
370   - <div ng-messages="theForm['mqtTtimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$error">
371   - <div translate ng-message="required">extension.required-key</div>
  370 + <div ng-messages="theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$error">
  371 + <div translate ng-message="required">extension.field-required</div>
372 372 </div>
373 373 </md-input-container>
374   - <md-input-container flex="40" class="md-block">
  374 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$invalid">
375 375 <label translate>extension.type</label>
376 376 <md-select required name="mqttTimeseriesType_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
377 377 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
... ... @@ -379,15 +379,15 @@
379 379 </md-option>
380 380 </md-select>
381 381 <div ng-messages="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$error">
382   - <div translate ng-message="required">extension.required-type</div>
  382 + <div translate ng-message="required">extension.field-required</div>
383 383 </div>
384 384 </md-input-container>
385 385 </section>
386   - <md-input-container class="md-block">
  386 + <md-input-container class="md-block" md-is-error="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$invalid">
387 387 <label translate>extension.value</label>
388 388 <input required name="mqttTimeseriesValue_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
389 389 <div ng-messages="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$error">
390   - <div translate ng-message="required">extension.required-value</div>
  390 + <div translate ng-message="required">extension.field-required</div>
391 391 </div>
392 392 </md-input-container>
393 393 </md-card-content>
... ... @@ -398,11 +398,8 @@
398 398 <div flex layout="row" layout-align="start center">
399 399 <md-button class="md-primary md-raised"
400 400 ng-click="addAttribute(map.converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
401   - <md-tooltip md-direction="top">
402   - {{ 'extension.add-timeseries' | translate }}
403   - </md-tooltip>
404 401 <md-icon class="material-icons">add</md-icon>
405   - <span translate>action.add</span>
  402 + <span translate>extension.add-timeseries</span>
406 403 </md-button>
407 404 </div>
408 405 </v-pane-content>
... ... @@ -417,16 +414,429 @@
417 414 <div flex layout="row" layout-align="start center">
418 415 <md-button class="md-primary md-raised"
419 416 ng-click="addMap(broker.mapping)" aria-label="{{ 'action.add' | translate }}">
420   - <md-tooltip md-direction="top">
421   - {{ 'extension.add-map' | translate }}
422   - </md-tooltip>
423 417 <md-icon class="material-icons">add</md-icon>
424   - <span translate>action.add</span>
  418 + <span translate>extension.add-map</span>
  419 + </md-button>
  420 + </div>
  421 + </v-pane-content>
  422 + </v-pane>
  423 + </v-accordion>
  424 +
  425 + <v-accordion id="mqtt-connect-requests-accordion" class="vAccordion--default">
  426 + <v-pane id="mqtt-connect-requests-pane">
  427 + <v-pane-header>
  428 + {{ 'extension.connect-requests' | translate }}
  429 + </v-pane-header>
  430 + <v-pane-content>
  431 + <div ng-if="broker.connectRequests.length > 0">
  432 + <ol class="list-group">
  433 + <li class="list-group-item" ng-repeat="(connectRequestIndex, connectRequest) in broker.connectRequests">
  434 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(connectRequest, broker.connectRequests)">
  435 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  436 + <md-tooltip md-direction="top">
  437 + {{ 'action.remove' | translate }}
  438 + </md-tooltip>
  439 + </md-button>
  440 + <md-card>
  441 + <md-card-content>
  442 + <md-input-container class="md-block">
  443 + <label translate>extension.topic-filter</label>
  444 + <input required name="conRequestTopicFilter_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.topicFilter">
  445 + <div ng-messages="theForm['conRequestTopicFilter_' + brokerIndex + connectRequestIndex].$error">
  446 + <div translate ng-message="required">extension.field-required</div>
  447 + </div>
  448 + </md-input-container>
  449 + <section flex layout="row">
  450 + <md-input-container flex="40" class="md-block" md-is-error="theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$touched && theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$invalid">
  451 + <label translate>extension.device-name-expression</label>
  452 + <md-select required name="connectDeviceNameExpression_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.nameExp" ng-change="changeNameExpression(connectRequest, 'connect')">
  453 + <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
  454 + {{value | translate}}
  455 + </md-option>
  456 + </md-select>
  457 + <div ng-messages="theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$error">
  458 + <div translate ng-message="required">extension.field-required</div>
  459 + </div>
  460 + </md-input-container>
  461 + <md-input-container ng-if="connectRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
  462 + <label translate>extension.json-name-expression</label>
  463 + <input required name="connectJsonNameExp_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.deviceNameJsonExpression">
  464 + <div ng-messages="theForm['connectJsonNameExp_' + brokerIndex + connectRequestIndex].$error">
  465 + <div translate ng-message="required">extension.field-required</div>
  466 + </div>
  467 + </md-input-container>
  468 + <md-input-container ng-if="connectRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
  469 + <label translate>extension.topic-name-expression</label>
  470 + <input required name="connectTopicNameExp_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.deviceNameTopicExpression">
  471 + <div ng-messages="theForm['connectTopicNameExp_' + brokerIndex + connectRequestIndex].$error">
  472 + <div translate ng-message="required">extension.field-required</div>
  473 + </div>
  474 + </md-input-container>
  475 + </section>
  476 + </md-card-content>
  477 + </md-card>
  478 + </li>
  479 + </ol>
  480 + </div>
  481 + <div flex layout="row" layout-align="start center">
  482 + <md-button class="md-primary md-raised"
  483 + ng-click='addConnectRequest(broker.connectRequests, "connect")' aria-label="{{ 'action.add' | translate }}">
  484 + <md-icon class="material-icons">add</md-icon>
  485 + <span translate>extension.add-connect-request</span>
  486 + </md-button>
  487 + </div>
  488 + </v-pane-content>
  489 + </v-pane>
  490 + </v-accordion>
  491 +
  492 + <v-accordion id="mqtt-disconnect-requests-accordion" class="vAccordion--default">
  493 + <v-pane id="mqtt-disconnect-requests-pane">
  494 + <v-pane-header>
  495 + {{ 'extension.disconnect-requests' | translate }}
  496 + </v-pane-header>
  497 + <v-pane-content>
  498 + <div ng-if="broker.disconnectRequests.length > 0">
  499 + <ol class="list-group">
  500 + <li class="list-group-item" ng-repeat="(disconnectRequestIndex, disconnectRequest) in broker.disconnectRequests">
  501 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(disconnectRequest, broker.disconnectRequests)">
  502 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  503 + <md-tooltip md-direction="top">
  504 + {{ 'action.remove' | translate }}
  505 + </md-tooltip>
  506 + </md-button>
  507 + <md-card>
  508 + <md-card-content>
  509 + <md-input-container class="md-block">
  510 + <label translate>extension.topic-filter</label>
  511 + <input required name="disconRequestTopicFilter_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.topicFilter">
  512 + <div ng-messages="theForm['disconRequestTopicFilter_' + brokerIndex + disconnectRequestIndex].$error">
  513 + <div translate ng-message="required">extension.field-required</div>
  514 + </div>
  515 + </md-input-container>
  516 + <section flex layout="row">
  517 + <md-input-container flex="40" class="md-block" md-is-error="theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$touched && theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$invalid">
  518 + <label translate>extension.device-name-expression</label>
  519 + <md-select required name="disconnectDeviceNameExpression_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.nameExp" ng-change="changeNameExpression(disconnectRequest, 'disconnect')">
  520 + <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
  521 + {{value | translate}}
  522 + </md-option>
  523 + </md-select>
  524 + <div ng-messages="theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$error">
  525 + <div translate ng-message="required">extension.field-required</div>
  526 + </div>
  527 + </md-input-container>
  528 + <md-input-container ng-if="disconnectRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
  529 + <label translate>extension.json-name-expression</label>
  530 + <input required name="disconnectJsonNameExp_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.deviceNameJsonExpression">
  531 + <div ng-messages="theForm['disconnectJsonNameExp_' + brokerIndex + disconnectRequestIndex].$error">
  532 + <div translate ng-message="required">extension.field-required</div>
  533 + </div>
  534 + </md-input-container>
  535 + <md-input-container ng-if="disconnectRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
  536 + <label translate>extension.topic-name-expression</label>
  537 + <input required name="disconnectTopicNameExp_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.deviceNameTopicExpression">
  538 + <div ng-messages="theForm['disconnectTopicNameExp_' + brokerIndex + disconnectRequestIndex].$error">
  539 + <div translate ng-message="required">extension.field-required</div>
  540 + </div>
  541 + </md-input-container>
  542 + </section>
  543 + </md-card-content>
  544 + </md-card>
  545 + </li>
  546 + </ol>
  547 + </div>
  548 + <div flex layout="row" layout-align="start center">
  549 + <md-button class="md-primary md-raised"
  550 + ng-click='addConnectRequest(broker.disconnectRequests, "disconnect")' aria-label="{{ 'action.add' | translate }}">
  551 + <md-icon class="material-icons">add</md-icon>
  552 + <span translate>extension.add-disconnect-request</span>
425 553 </md-button>
426 554 </div>
427 555 </v-pane-content>
428 556 </v-pane>
429 557 </v-accordion>
  558 +
  559 + <v-accordion id="mqtt-attribute-requests-accordion" class="vAccordion--default">
  560 + <v-pane id="mqtt-attribute-requests-pane">
  561 + <v-pane-header>
  562 + {{ 'extension.attribute-requests' | translate }}
  563 + </v-pane-header>
  564 + <v-pane-content>
  565 + <div ng-if="broker.attributeRequests.length > 0">
  566 + <ol class="list-group">
  567 + <li class="list-group-item" ng-repeat="(attributeRequestIndex, attributeRequest) in broker.attributeRequests">
  568 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attributeRequest, broker.attributeRequests)">
  569 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  570 + <md-tooltip md-direction="top">
  571 + {{ 'action.remove' | translate }}
  572 + </md-tooltip>
  573 + </md-button>
  574 + <md-card>
  575 + <md-card-content>
  576 + <section flex layout="row">
  577 + <md-input-container flex="80" class="md-block">
  578 + <label translate>extension.topic-filter</label>
  579 + <input required name="attributeRequestTopicFilter_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.topicFilter">
  580 + <div ng-messages="theForm['attributeRequestTopicFilter_' + brokerIndex + attributeRequestIndex].$error">
  581 + <div translate ng-message="required">extension.field-required</div>
  582 + </div>
  583 + </md-input-container>
  584 + <md-input-container flex="20" class="md-block">
  585 + <md-checkbox flex aria-label="{{ 'extension.client-scope' | translate }}"
  586 + ng-model="attributeRequest.clientScope">{{ 'extension.client-scope' | translate }}
  587 + </md-checkbox>
  588 + </md-input-container>
  589 + </section>
  590 + <section flex layout="row">
  591 + <md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$invalid">
  592 + <label translate>extension.device-name-expression</label>
  593 + <md-select required name="attrRequestDeviceNameExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.nameExp" ng-change="changeNameExpression(attributeRequest, 'attribute')">
  594 + <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
  595 + {{value | translate}}
  596 + </md-option>
  597 + </md-select>
  598 + <div ng-messages="theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$error">
  599 + <div translate ng-message="required">extension.field-required</div>
  600 + </div>
  601 + </md-input-container>
  602 + <md-input-container ng-if="attributeRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
  603 + <label translate>extension.json-name-expression</label>
  604 + <input required name="attrRequestJsonNameExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.deviceNameJsonExpression">
  605 + <div ng-messages="theForm['attrRequestJsonNameExp_' + brokerIndex + attributeRequestIndex].$error">
  606 + <div translate ng-message="required">extension.field-required</div>
  607 + </div>
  608 + </md-input-container>
  609 + <md-input-container ng-if="attributeRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
  610 + <label translate>extension.topic-name-expression</label>
  611 + <input required name="attrRequestTopicNameExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.deviceNameTopicExpression">
  612 + <div ng-messages="theForm['attrRequestTopicNameExp_' + brokerIndex + attributeRequestIndex].$error">
  613 + <div translate ng-message="required">extension.field-required</div>
  614 + </div>
  615 + </md-input-container>
  616 + </section>
  617 +
  618 + <section flex layout="row">
  619 + <md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$invalid">
  620 + <label translate>extension.attribute-key-expression</label>
  621 + <md-select required name="attrRequestAttributeKeyExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attrKey" ng-change="changeAttrKeyExpression(attributeRequest)">
  622 + <md-option ng-repeat="(key, value) in attributeKeyExpressions" ng-value='key'>
  623 + {{value | translate}}
  624 + </md-option>
  625 + </md-select>
  626 + <div ng-messages="theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$error">
  627 + <div translate ng-message="required">extension.field-required</div>
  628 + </div>
  629 + </md-input-container>
  630 + <md-input-container ng-if="attributeRequest.attrKey == 'attributeKeyJsonExpression'" flex="60" class="md-block">
  631 + <label translate>extension.attr-json-key-expression</label>
  632 + <input required name="attrRequestJsonKeyExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attributeKeyJsonExpression">
  633 + <div ng-messages="theForm['attrRequestJsonKeyExp_' + brokerIndex + attributeRequestIndex].$error">
  634 + <div translate ng-message="required">extension.field-required</div>
  635 + </div>
  636 + </md-input-container>
  637 + <md-input-container ng-if="attributeRequest.attrKey == 'attributeKeyTopicExpression'" flex="60" class="md-block">
  638 + <label translate>extension.attr-topic-key-expression</label>
  639 + <input required name="attrRequestTopicKeyExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attributeKeyTopicExpression">
  640 + <div ng-messages="theForm['attrRequestTopicKeyExp_' + brokerIndex + attributeRequestIndex].$error">
  641 + <div translate ng-message="required">extension.field-required</div>
  642 + </div>
  643 + </md-input-container>
  644 + </section>
  645 +
  646 + <section flex layout="row">
  647 + <md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$invalid">
  648 + <label translate>extension.request-id-expression</label>
  649 + <md-select required name="attrRequestIdExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestId" ng-change="changeRequestIdExpression(attributeRequest)">
  650 + <md-option ng-repeat="(key, value) in requestIdExpressions" ng-value='key'>
  651 + {{value | translate}}
  652 + </md-option>
  653 + </md-select>
  654 + <div ng-messages="theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$error">
  655 + <div translate ng-message="required">extension.field-required</div>
  656 + </div>
  657 + </md-input-container>
  658 + <md-input-container ng-if="attributeRequest.requestId == 'requestIdJsonExpression'" flex="60" class="md-block">
  659 + <label translate>extension.request-id-json-expression</label>
  660 + <input required name="attrRequestJsonIdExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestIdJsonExpression">
  661 + <div ng-messages="theForm['attrRequestJsonIdExp_' + brokerIndex + attributeRequestIndex].$error">
  662 + <div translate ng-message="required">extension.field-required</div>
  663 + </div>
  664 + </md-input-container>
  665 + <md-input-container ng-if="attributeRequest.requestId == 'requestIdTopicExpression'" flex="60" class="md-block">
  666 + <label translate>extension.request-id-topic-expression</label>
  667 + <input required name="attrRequestTopicIdExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestIdTopicExpression">
  668 + <div ng-messages="theForm['attrRequestTopicIdExp_' + brokerIndex + attributeRequestIndex].$error">
  669 + <div translate ng-message="required">extension.field-required</div>
  670 + </div>
  671 + </md-input-container>
  672 + </section>
  673 +
  674 + <md-input-container class="md-block">
  675 + <label translate>extension.response-topic-expression</label>
  676 + <input required name="attributeRequestResponseTopicExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.responseTopicExpression">
  677 + <div ng-messages="theForm['attributeRequestResponseTopicExp_' + brokerIndex + attributeRequestIndex].$error">
  678 + <div translate ng-message="required">extension.field-required</div>
  679 + </div>
  680 + </md-input-container>
  681 + <md-input-container class="md-block">
  682 + <label translate>extension.value-expression</label>
  683 + <input required name="attributeRequestValueExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.valueExpression">
  684 + <div ng-messages="theForm['attributeRequestValueExp_' + brokerIndex + attributeRequestIndex].$error">
  685 + <div translate ng-message="required">extension.field-required</div>
  686 + </div>
  687 + </md-input-container>
  688 + </md-card-content>
  689 + </md-card>
  690 + </li>
  691 + </ol>
  692 + </div>
  693 + <div flex layout="row" layout-align="start center">
  694 + <md-button class="md-primary md-raised"
  695 + ng-click="addAttributeRequest(broker.attributeRequests)" aria-label="{{ 'action.add' | translate }}">
  696 + <md-icon class="material-icons">add</md-icon>
  697 + <span translate>extension.add-attribute-request</span>
  698 + </md-button>
  699 + </div>
  700 + </v-pane-content>
  701 + </v-pane>
  702 + </v-accordion>
  703 +
  704 + <v-accordion id="mqtt-attribute-updates-accordion" class="vAccordion--default">
  705 + <v-pane id="mqtt-attribute-updates-pane">
  706 + <v-pane-header>
  707 + {{ 'extension.attribute-updates' | translate }}
  708 + </v-pane-header>
  709 + <v-pane-content>
  710 + <div ng-if="broker.attributeUpdates.length > 0">
  711 + <ol class="list-group">
  712 + <li class="list-group-item" ng-repeat="(attributeUpdateIndex, attributeUpdate) in broker.attributeUpdates">
  713 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attributeUpdate, broker.attributeUpdates)">
  714 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  715 + <md-tooltip md-direction="top">
  716 + {{ 'action.remove' | translate }}
  717 + </md-tooltip>
  718 + </md-button>
  719 + <md-card>
  720 + <md-card-content>
  721 + <section flex layout="row">
  722 + <md-input-container flex="50" class="md-block">
  723 + <label translate>extension.device-name-filter</label>
  724 + <input required name="attributeUpdateDeviceNameFilter_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.deviceNameFilter">
  725 + <div ng-messages="theForm['attributeUpdateDeviceNameFilter_' + brokerIndex + attributeUpdateIndex].$error">
  726 + <div translate ng-message="required">extension.field-required</div>
  727 + </div>
  728 + </md-input-container>
  729 + <md-input-container flex="50" class="md-block">
  730 + <label translate>extension.attribute-filter</label>
  731 + <input required name="attributeUpdateAttributeFilter_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.attributeFilter">
  732 + <div ng-messages="theForm['attributeUpdateAttributeFilter_' + brokerIndex + attributeUpdateIndex].$error">
  733 + <div translate ng-message="required">extension.field-required</div>
  734 + </div>
  735 + </md-input-container>
  736 + </section>
  737 + <md-input-container class="md-block">
  738 + <label translate>extension.topic-expression</label>
  739 + <input required name="attributeUpdateTopicExp_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.topicExpression">
  740 + <div ng-messages="theForm['attributeUpdateTopicExp_' + brokerIndex + attributeUpdateIndex].$error">
  741 + <div translate ng-message="required">extension.field-required</div>
  742 + </div>
  743 + </md-input-container>
  744 + <md-input-container class="md-block">
  745 + <label translate>extension.value-expression</label>
  746 + <input required name="attributeUpdateValueExp_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.valueExpression">
  747 + <div ng-messages="theForm['attributeUpdateValueExp_' + brokerIndex + attributeUpdateIndex].$error">
  748 + <div translate ng-message="required">extension.field-required</div>
  749 + </div>
  750 + </md-input-container>
  751 + </md-card-content>
  752 + </md-card>
  753 + </li>
  754 + </ol>
  755 + </div>
  756 + <div flex layout="row" layout-align="start center">
  757 + <md-button class="md-primary md-raised"
  758 + ng-click='addAttributeUpdate(broker.attributeUpdates)' aria-label="{{ 'action.add' | translate }}">
  759 + <md-icon class="material-icons">add</md-icon>
  760 + <span translate>extension.add-attribute-update</span>
  761 + </md-button>
  762 + </div>
  763 + </v-pane-content>
  764 + </v-pane>
  765 + </v-accordion>
  766 +
  767 + <v-accordion id="mqtt-server-side-rpc-accordion" class="vAccordion--default">
  768 + <v-pane id="mqtt-server-side-rpc-pane">
  769 + <v-pane-header>
  770 + {{ 'extension.server-side-rpc' | translate }}
  771 + </v-pane-header>
  772 + <v-pane-content>
  773 + <div ng-if="broker.serverSideRpc.length > 0">
  774 + <ol class="list-group">
  775 + <li class="list-group-item" ng-repeat="(rpcIndex, rpc) in broker.serverSideRpc">
  776 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(rpc, broker.serverSideRpc)">
  777 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  778 + <md-tooltip md-direction="top">
  779 + {{ 'action.remove' | translate }}
  780 + </md-tooltip>
  781 + </md-button>
  782 + <md-card>
  783 + <md-card-content>
  784 + <section flex layout="row">
  785 + <md-input-container flex="50" class="md-block">
  786 + <label translate>extension.device-name-filter</label>
  787 + <input required name="serverSideRpcDeviceNameFilter_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.deviceNameFilter">
  788 + <div ng-messages="theForm['serverSideRpcDeviceNameFilter_' + brokerIndex + rpcIndex].$error">
  789 + <div translate ng-message="required">extension.field-required</div>
  790 + </div>
  791 + </md-input-container>
  792 + <md-input-container flex="50" class="md-block">
  793 + <label translate>extension.method-filter</label>
  794 + <input required name="serverSideRpcMethodFilter_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.methodFilter">
  795 + <div ng-messages="theForm['serverSideRpcMethodFilter_' + brokerIndex + rpcIndex].$error">
  796 + <div translate ng-message="required">extension.field-required</div>
  797 + </div>
  798 + </md-input-container>
  799 + </section>
  800 + <md-input-container class="md-block">
  801 + <label translate>extension.request-topic-expression</label>
  802 + <input required name="serverSideRpcRequestTopicExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.requestTopicExpression">
  803 + <div ng-messages="theForm['serverSideRpcRequestTopicExp_' + brokerIndex + rpcIndex].$error">
  804 + <div translate ng-message="required">extension.field-required</div>
  805 + </div>
  806 + </md-input-container>
  807 + <md-input-container class="md-block">
  808 + <label translate>extension.response-topic-expression</label>
  809 + <input name="serverSideRpcResponseTopicExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.responseTopicExpression" parse-to-null>
  810 + </md-input-container>
  811 + <section flex layout="row">
  812 + <md-input-container flex="50" class="md-block">
  813 + <label translate>extension.response-timeout</label>
  814 + <input type="number" name="serverSideRpcResponseTimeout_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.responseTimeout" parse-to-null>
  815 + </md-input-container>
  816 + <md-input-container flex="50" class="md-block">
  817 + <label translate>extension.value-expression</label>
  818 + <input required name="serverSideRpcValueExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.valueExpression">
  819 + <div ng-messages="theForm['serverSideRpcValueExp_' + brokerIndex + rpcIndex].$error">
  820 + <div translate ng-message="required">extension.field-required</div>
  821 + </div>
  822 + </md-input-container>
  823 + </section>
  824 + </md-card-content>
  825 + </md-card>
  826 + </li>
  827 + </ol>
  828 + </div>
  829 + <div flex layout="row" layout-align="start center">
  830 + <md-button class="md-primary md-raised"
  831 + ng-click='addServerSideRpc(broker.serverSideRpc)' aria-label="{{ 'action.add' | translate }}">
  832 + <md-icon class="material-icons">add</md-icon>
  833 + <span translate>extension.add-server-side-rpc-request</span>
  834 + </md-button>
  835 + </div>
  836 + </v-pane-content>
  837 + </v-pane>
  838 + </v-accordion>
  839 +
430 840 </md-card-content>
431 841 </md-card>
432 842 </li>
... ... @@ -436,18 +846,15 @@
436 846 <div flex layout="row" layout-align="start center">
437 847 <md-button class="md-primary md-raised"
438 848 ng-click="addBroker()" aria-label="{{ 'action.add' | translate }}">
439   - <md-tooltip md-direction="top">
440   - {{ 'extension.add-broker' | translate }}
441   - </md-tooltip>
442 849 <md-icon class="material-icons">add</md-icon>
443   - <span translate>action.add</span>
  850 + <span translate>extension.add-broker</span>
444 851 </md-button>
445 852 </div>
446 853 </v-pane-content>
447 854 </v-pane>
448 855 </v-accordion>
449   -<pre>
  856 +<!--<pre>
450 857 {{config | json}}
451   -</pre>
  858 +</pre>-->
452 859 </md-card-content>
453 860 </md-card>
... ...
  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,543 @@
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.field-required</div>
  60 + </div>
  61 + </md-input-container>
  62 +
  63 +
  64 + <md-input-container flex="50" class="md-block" md-is-error="theForm['applicationUri_' + serverIndex].$touched && theForm['applicationUri_' + serverIndex].$invalid">
  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.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.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.field-required</div>
  80 + </div>
  81 + </md-input-container>
  82 +
  83 + <md-input-container flex="50" class="md-block">
  84 + <label translate>extension.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.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.field-required</div>
  117 + </div>
  118 + </md-input-container>
  119 +
  120 + <md-input-container flex="50" class="md-block">
  121 + <label translate>extension.timeout</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.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.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 | translate"></span></md-option>
  161 + </md-select>
  162 + <div ng-messages="theForm['identityType_' + serverIndex].$error">
  163 + <div translate
  164 + ng-message="required"
  165 + >extension.field-required</div>
  166 + </div>
  167 + </md-input-container>
  168 + </div>
  169 +
  170 + <div ng-if="server.identity.type != 'username'">
  171 + <span class=""
  172 + ng-init="server.identity = {'type':'anonymous'}"></span>
  173 + </div>
  174 + <div layout="row" ng-if="server.identity.type == 'username'">
  175 + <md-input-container flex="50" class="md-block" md-is-error="theForm['identityUsername_' + serverIndex].$touched && theForm['identityUsername_' + serverIndex].$invalid">
  176 + <label translate>extension.username</label>
  177 + <input required
  178 + name="identityUsername_{{serverIndex}}"
  179 + ng-model="server.identity.username"
  180 + >
  181 + <div ng-messages="theForm['identityUsername_' + serverIndex].$error">
  182 + <div translate
  183 + ng-message="required"
  184 + >extension.field-required</div>
  185 + </div>
  186 + </md-input-container>
  187 +
  188 + <md-input-container flex="50" class="md-block" md-is-error="theForm['identityPassword_' + serverIndex].$touched && theForm['identityPassword_' + serverIndex].$invalid">
  189 + <label translate>extension.password</label>
  190 + <input required
  191 + name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
  192 + <div ng-messages="theForm['identityPassword_' + serverIndex].$error">
  193 + <div translate
  194 + ng-message="required"
  195 + >extension.field-required</div>
  196 + </div>
  197 + </md-input-container>
  198 + </div>
  199 +
  200 + <v-accordion id="opc-attributes-accordion" class="vAccordion--default">
  201 + <v-pane id="opc-attributes-pane" expanded="true">
  202 + <v-pane-header>
  203 + {{ 'extension.opc-keystore' | translate }}
  204 + </v-pane-header>
  205 + <v-pane-content>
  206 +
  207 + <md-input-container class="md-block tb-container-for-select">
  208 + <label translate>extension.opc-keystore-type</label>
  209 + <md-select required name="keystoreType_{{serverIndex}}" ng-model="server.keystore.type">
  210 + <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
  211 + </md-select>
  212 + <div ng-messages="theForm['keystoreType_'+serverIndex].$error">
  213 + <div translate ng-message="required">extension.field-required</div>
  214 + </div>
  215 + </md-input-container>
  216 +
  217 + <section class="dropdown-section">
  218 + <div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}">
  219 + <span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
  220 + <label class="tb-label" translate>extension.opc-keystore-location</label>
  221 + <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
  222 + <div class="tb-file-clear-container">
  223 + <md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
  224 + <md-tooltip md-direction="top">
  225 + {{ 'action.remove' | translate }}
  226 + </md-tooltip>
  227 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
  228 + </md-button>
  229 + </div>
  230 + <div class="alert tb-flow-drop" flow-drop>
  231 + <label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
  232 + <input flow-attrs="{accept:'.pfx,.p12'}"
  233 + type="file"
  234 + class="file-input"
  235 + flow-btn id="dropFileKeystore_{{serverIndex}}"
  236 + name="keystoreFile"
  237 + ng-model="server.keystore.file"
  238 + >
  239 + </div>
  240 + </div>
  241 + </div>
  242 + <div class="dropdown-messages">
  243 + <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
  244 + <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
  245 + </div>
  246 + </section>
  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.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.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.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.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.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.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.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.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.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-icon class="material-icons">add</md-icon>
  421 + <span translate>add-attribute</span>
  422 + </md-button>
  423 + </div>
  424 + </v-pane-content>
  425 + </v-pane>
  426 + </v-accordion>
  427 +
  428 + <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
  429 + <v-pane id="opc-timeseries-pane">
  430 + <v-pane-header>
  431 + {{ 'extension.timeseries' | translate }}
  432 + </v-pane-header>
  433 + <v-pane-content>
  434 + <div ng-show="map.timeseries.length > 0">
  435 + <ol class="list-group">
  436 + <li class="list-group-item"
  437 + ng-repeat="(timeseriesIndex, timeseries) in map.timeseries"
  438 + >
  439 + <md-button aria-label="{{ 'action.remove' | translate }}"
  440 + class="md-icon-button"
  441 + ng-click="removeItem(timeseries, map.timeseries)"
  442 + >
  443 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  444 + <md-tooltip md-direction="top">
  445 + {{ 'action.remove' | translate }}
  446 + </md-tooltip>
  447 + </md-button>
  448 + <md-card>
  449 + <md-card-content>
  450 + <section flex layout="row">
  451 + <md-input-container flex="60" class="md-block">
  452 + <label translate>extension.key</label>
  453 + <input required
  454 + name="opcTimeseriesKey_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
  455 + ng-model="timeseries.key"
  456 + >
  457 + <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
  458 + <div translate
  459 + ng-message="required"
  460 + >extension.field-required</div>
  461 + </div>
  462 + </md-input-container>
  463 + <md-input-container flex="40"
  464 + class="md-block tb-container-for-select"
  465 + >
  466 + <label translate>extension.type</label>
  467 + <md-select required
  468 + name="opcTimeseriesType_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}"
  469 + ng-model="timeseries.type"
  470 + >
  471 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType"
  472 + ng-value="attrType"
  473 + >
  474 + {{attrTypeValue | translate}}
  475 + </md-option>
  476 + </md-select>
  477 + <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
  478 + <div translate
  479 + ng-message="required"
  480 + >extension.field-required</div>
  481 + </div>
  482 + </md-input-container>
  483 + </section>
  484 + <section flex layout="row">
  485 + <md-input-container flex="100" class="md-block">
  486 + <label translate>extension.value</label>
  487 + <input required name="opcTimeseriesValue_{{serverIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
  488 + <div ng-messages="theForm['opcTimeseriesValue_' + serverIndex + mapIndex + timeseriesIndex].$error">
  489 + <div translate ng-message="required">extension.required-value</div>
  490 + </div>
  491 + </md-input-container>
  492 + </section>
  493 + </md-card-content>
  494 + </md-card>
  495 + </li>
  496 + </ol>
  497 + </div>
  498 + <div flex layout="row" layout-align="start center">
  499 + <md-button class="md-primary md-raised"
  500 + ng-click="addNewAttribute(map.timeseries)"
  501 + aria-label="{{ 'action.add' | translate }}"
  502 + >
  503 + <md-icon class="material-icons">add</md-icon>
  504 + <span translate>extension.add-timeseries</span>
  505 + </md-button>
  506 + </div>
  507 + </v-pane-content>
  508 + </v-pane>
  509 + </v-accordion>
  510 +
  511 +
  512 + </md-card-content>
  513 + </md-card>
  514 + </li>
  515 + </ol>
  516 + </div>
  517 + <div flex
  518 + layout="row"
  519 + layout-align="start center"
  520 + >
  521 + <md-button class="md-primary md-raised"
  522 + ng-click="addMap(server.mapping)"
  523 + aria-label="{{ 'action.add' | translate }}"
  524 + >
  525 + <md-icon class="material-icons">add</md-icon>
  526 + <span translate>extension.add-map</span>
  527 + </md-button>
  528 + </div>
  529 + </v-pane-content>
  530 + </v-pane>
  531 + </v-accordion>
  532 +
  533 + </md-card-content>
  534 + </md-card>
  535 + </li>
  536 + </ol>
  537 +
  538 + <div flex
  539 + layout="row"
  540 + layout-align="start center"
  541 + >
  542 + <md-button class="md-primary md-raised"
  543 + ng-click="addServer(configuration.servers)"
  544 + aria-label="{{ 'action.add' | translate }}"
  545 + >
  546 + <md-icon class="material-icons">add</md-icon>
  547 + <span translate>extension.opc-add-server</span>
  548 + </md-button>
  549 + </div>
  550 +
  551 + </div>
  552 + </v-pane-content>
  553 + </v-pane>
  554 + </v-accordion>
  555 + <!--{{config}}-->
  556 + </md-card-content>
  557 +</md-card>
\ No newline at end of file
... ...
... ... @@ -22,9 +22,6 @@
22 22 margin-top: 0;
23 23 padding-left: 3px;
24 24 }
25   - .t-right {
26   - text-align: right;
27   - }
28 25 .tb-container {
29 26 width:100%;
30 27 }
... ... @@ -33,6 +30,15 @@
33 30 padding: 5px 0 0 0;
34 31 }
35 32 }
  33 + .dropdown-section {
  34 + margin-bottom: 30px;
  35 + }
  36 +}
  37 +
  38 +.extension-form.extension-mqtt {
  39 + md-checkbox{
  40 + margin-left: 10px;
  41 + }
36 42 }
37 43
38 44 .tb-extension-custom-transformer-panel {
... ... @@ -48,4 +54,20 @@
48 54 .ace_text-input {
49 55 position:absolute!important
50 56 }
  57 +}
  58 +
  59 +.extensionDialog {
  60 + min-width: 1000px;
  61 +}
  62 +
  63 +.tb-container-for-select {
  64 + height: 58px;
  65 +}
  66 +
  67 +.tb-drop-file-input-hide {
  68 + height: 200%;
  69 + display: block;
  70 + position: absolute;
  71 + bottom: 0;
  72 + width: 100%;
51 73 }
\ No newline at end of file
... ...
... ... @@ -17,11 +17,13 @@
17 17 import ExtensionTableDirective from './extension-table.directive';
18 18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
19 19 import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
  20 +import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
20 21 import {ParseToNull} from './extension-dialog.controller';
21 22
22 23 export default angular.module('thingsboard.extension', [])
23 24 .directive('tbExtensionTable', ExtensionTableDirective)
24 25 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
25 26 .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
  27 + .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
26 28 .directive('parseToNull', ParseToNull)
27 29 .name;
\ No newline at end of file
... ...
... ... @@ -739,12 +739,7 @@ export default angular.module('thingsboard.locale', [])
739 739 "extension-id": "Extension id",
740 740 "extension-type": "Extension type",
741 741 "transformer-json": "JSON *",
742   - "id-required": "Extension id is required.",
743 742 "unique-id-required": "Current extension id already exists.",
744   - "type-required": "Extension type is required.",
745   - "required-type": "Type is required.",
746   - "required-key": "Key is required.",
747   - "required-value": "Value is required.",
748 743 "delete": "Delete extension",
749 744 "add": "Add extension",
750 745 "edit": "Edit extension",
... ... @@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', [])
754 749 "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
755 750 "converters": "Converters",
756 751 "converter-id": "Converter id",
757   - "converter-id-required": "Converter id is required.",
758 752 "configuration": "Configuration",
759 753 "converter-configurations": "Converter configurations",
760 754 "token": "Security token",
761 755 "add-converter": "Add converter",
762   - "add-converter-prompt": "Please add converter",
763 756 "add-config": "Add converter configuration",
764   - "add-config-prompt": "Please add converter configuration",
765 757 "device-name-expression": "Device name expression",
766   - "device-name-expression-required": "Device name expression is required.",
767 758 "device-type-expression": "Device type expression",
768   - "device-type-expression-required": "Device type expression is required.",
769 759 "custom": "Custom",
770 760 "to-double": "To Double",
771 761 "transformer": "Transformer",
... ... @@ -773,25 +763,20 @@ export default angular.module('thingsboard.locale', [])
773 763 "json-parse": "Unable to parse transformer json.",
774 764 "attributes": "Attributes",
775 765 "add-attribute": "Add attribute",
  766 + "add-map": "Add mapping element",
776 767 "timeseries": "Timeseries",
777 768 "add-timeseries": "Add timeseries",
778   -
  769 + "field-required": "Field is required",
779 770 "brokers": "Brokers",
780 771 "add-broker": "Add broker",
781   - "add-broker-prompt": "Please add broker",
782 772 "host": "Host",
783   - "host-required": "Host is required.",
784 773 "port": "Port",
785   - "port-required": "Port is required.",
786 774 "port-range": "Port should be in a range from 1 to 65535.",
787 775 "ssl": "Ssl",
788 776 "credentials": "Credentials",
789 777 "username": "Username",
790   - "username-required": "Username is required.",
791 778 "password": "Password",
792   - "password-required": "Password is required.",
793   - "retry-interval": "Retry interval",
794   - "retry-interval-required": "Retry interval is required.",
  779 + "retry-interval": "Retry interval in milliseconds",
795 780 "anonymous": "Anonymous",
796 781 "basic": "Basic",
797 782 "pem": "PEM",
... ... @@ -801,28 +786,59 @@ export default angular.module('thingsboard.locale', [])
801 786 "no-file": "No file selected.",
802 787 "drop-file": "Drop a file or click to select a file to upload.",
803 788 "mapping": "Mapping",
804   - "add-map": "Add map",
805 789 "topic-filter": "Topic filter",
806   - "topic-filter-required": "Topic filter is required.",
807 790 "converter-type": "Converter type",
808   - "converter-type-required": "Converter type is required.",
809 791 "converter-json": "Json",
810   - "name-expression": "Name expression",
811   - "type-expression": "Type expression",
812   - "json-name-expression": "Json name expression",
813   - "json-name-expression-required": "Json name expression is required.",
814   - "topic-name-expression": "Topic name expression",
815   - "topic-name-expression-required": "Topic name expression is required.",
816   - "json-type-expression": "Json type expression",
817   - "json-type-expression-required": "Json type expression is required.",
818   - "topic-type-expression": "Topic type expression",
819   - "topic-type-expression-required": "Topic type expression is required.",
  792 + "json-name-expression": "Device name json expression",
  793 + "topic-name-expression": "Device name topic expression",
  794 + "json-type-expression": "Device type json expression",
  795 + "topic-type-expression": "Device type topic expression",
  796 + "attribute-key-expression": "Attribute key expression",
  797 + "attr-json-key-expression": "Attribute key json expression",
  798 + "attr-topic-key-expression": "Attribute key topic expression",
  799 + "request-id-expression": "Request id expression",
  800 + "request-id-json-expression": "Request id json expression",
  801 + "request-id-topic-expression": "Request id topic expression",
  802 + "response-topic-expression": "Response topic expression",
  803 + "value-expression": "Value expression",
820 804 "topic": "Topic",
821   - "timeout": "Timeout",
  805 + "timeout": "Timeout in milliseconds",
822 806 "converter-json-required": "Converter json is required.",
823 807 "converter-json-parse": "Unable to parse converter json.",
824 808 "filter-expression": "Filter expression",
825   - "filter-expression-required": "Filter expression is required."
  809 + "connect-requests": "Connect requests",
  810 + "add-connect-request": "Add connect request",
  811 + "disconnect-requests": "Disconnect requests",
  812 + "add-disconnect-request": "Add disconnect request",
  813 + "attribute-requests": "Attribute requests",
  814 + "add-attribute-request": "Add attribute request",
  815 + "attribute-updates": "Attribute updates",
  816 + "add-attribute-update": "Add attribute update",
  817 + "server-side-rpc": "Server side RPC",
  818 + "add-server-side-rpc-request": "Add server-side RPC request",
  819 + "device-name-filter": "Device name filter",
  820 + "attribute-filter": "Attribute filter",
  821 + "method-filter": "Method filter",
  822 + "request-topic-expression": "Request topic expression",
  823 + "response-timeout": "Response timeout in milliseconds",
  824 + "topic-expression": "Topic expression",
  825 + "client-scope": "Client scope",
  826 + "opc-server": "Servers",
  827 + "opc-add-server": "Add server",
  828 + "opc-application-name": "Application name",
  829 + "opc-application-uri": "Application uri",
  830 + "opc-scan-period-in-seconds": "Scan period in seconds",
  831 + "opc-security": "Security",
  832 + "opc-identity": "Identity",
  833 + "opc-keystore": "Keystore",
  834 + "opc-type": "Type",
  835 + "opc-keystore-type":"Type",
  836 + "opc-keystore-location":"Location *",
  837 + "opc-keystore-password":"Password",
  838 + "opc-keystore-alias":"Alias",
  839 + "opc-keystore-key-password":"Key password",
  840 + "opc-device-node-pattern":"Device node pattern",
  841 + "opc-device-name-pattern":"Device name pattern",
826 842 },
827 843 "fullscreen": {
828 844 "expand": "Expand to fullscreen",
... ...