Commit 33451e059759920679baa5d0d4dc2b54d88c2ba0

Authored by Sergey Tarnavskiy
2 parents dad2a26f f00898c7

Add mqtt form

@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 "angular-material": "1.1.1", 30 "angular-material": "1.1.1",
31 "angular-material-data-table": "^0.10.9", 31 "angular-material-data-table": "^0.10.9",
32 "angular-material-icons": "^0.7.1", 32 "angular-material-icons": "^0.7.1",
  33 + "angular-material-expansion-panel": "^0.7.2",
33 "angular-messages": "1.5.8", 34 "angular-messages": "1.5.8",
34 "angular-route": "1.5.8", 35 "angular-route": "1.5.8",
35 "angular-sanitize": "1.5.8", 36 "angular-sanitize": "1.5.8",
@@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router'; @@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router';
39 import angularJwt from 'angular-jwt'; 39 import angularJwt from 'angular-jwt';
40 import 'angular-drag-and-drop-lists'; 40 import 'angular-drag-and-drop-lists';
41 import mdDataTable from 'angular-material-data-table'; 41 import mdDataTable from 'angular-material-data-table';
  42 +import 'angular-material-expansion-panel';
42 import ngTouch from 'angular-touch'; 43 import ngTouch from 'angular-touch';
43 import 'angular-carousel'; 44 import 'angular-carousel';
44 import 'clipboard'; 45 import 'clipboard';
@@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css'; @@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css';
82 import 'mdPickers/dist/mdPickers.min.css'; 83 import 'mdPickers/dist/mdPickers.min.css';
83 import 'angular-hotkeys/build/hotkeys.min.css'; 84 import 'angular-hotkeys/build/hotkeys.min.css';
84 import 'angular-carousel/dist/angular-carousel.min.css'; 85 import 'angular-carousel/dist/angular-carousel.min.css';
  86 +import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
85 import '../scss/main.scss'; 87 import '../scss/main.scss';
86 88
87 import AppConfig from './app.config'; 89 import AppConfig from './app.config';
@@ -103,6 +105,7 @@ angular.module('thingsboard', [ @@ -103,6 +105,7 @@ angular.module('thingsboard', [
103 angularJwt, 105 angularJwt,
104 'dndLists', 106 'dndLists',
105 mdDataTable, 107 mdDataTable,
  108 + 'material.components.expansionPanels',
106 ngTouch, 109 ngTouch,
107 'angular-carousel', 110 'angular-carousel',
108 'ngclipboard', 111 'ngclipboard',
@@ -350,6 +350,20 @@ export default angular.module('thingsboard.types', []) @@ -350,6 +350,20 @@ export default angular.module('thingsboard.types', [])
350 name: "extension.pem" 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 latestTelemetry: { 367 latestTelemetry: {
354 value: "LATEST_TELEMETRY", 368 value: "LATEST_TELEMETRY",
355 name: "attribute.scope-latest-telemetry", 369 name: "attribute.scope-latest-telemetry",
@@ -29,45 +29,88 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -29,45 +29,88 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
29 vm.entityId = entityId; 29 vm.entityId = entityId;
30 vm.allExtensions = allExtensions; 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 function cancel() { 61 function cancel() {
45 $mdDialog.cancel(); 62 $mdDialog.cancel();
46 } 63 }
  64 +
  65 + vm.save = save;
47 function save() { 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 } else { 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 vm.validateId = function() { 111 vm.validateId = function() {
69 var coincidenceArray = vm.allExtensions.filter(function(ext) { 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 if(coincidenceArray.length) { 115 if(coincidenceArray.length) {
73 if(!vm.isAdd) { 116 if(!vm.isAdd) {
@@ -82,11 +125,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -82,11 +125,11 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
82 } else { 125 } else {
83 $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true); 126 $scope.theForm.extensionId.$setValidity('uniqueIdValidation', true);
84 } 127 }
85 - } 128 + };
86 129
87 function saveTransformers() { 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 if(config && config.length > 0) { 133 if(config && config.length > 0) {
91 for(let i=0;i<config.length;i++) { 134 for(let i=0;i<config.length;i++) {
92 for(let j=0;j<config[i].converters.length;j++){ 135 for(let j=0;j<config[i].converters.length;j++){
@@ -106,8 +149,8 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -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 if(brokers && brokers.length > 0) { 154 if(brokers && brokers.length > 0) {
112 for(let i=0;i<brokers.length;i++) { 155 for(let i=0;i<brokers.length;i++) {
113 if(brokers[i].mapping && brokers[i].mapping.length > 0) { 156 if(brokers[i].mapping && brokers[i].mapping.length > 0) {
@@ -119,6 +162,27 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -119,6 +162,27 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
119 delete brokers[i].mapping[j].converterType; 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,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,8 +15,8 @@
15 limitations under the License. 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 <md-toolbar> 20 <md-toolbar>
21 <div class="md-toolbar-tools"> 21 <div class="md-toolbar-tools">
22 <h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2> 22 <h2 translate>{{ vm.isAdd ? 'extension.add' : 'extension.edit'}}</h2>
@@ -26,49 +26,54 @@ @@ -26,49 +26,54 @@
26 </md-button> 26 </md-button>
27 </div> 27 </div>
28 </md-toolbar> 28 </md-toolbar>
  29 +
29 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> 30 <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  31 +
30 <span style="min-height: 5px;" flex="" ng-show="!loading"></span> 32 <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  33 +
31 <md-dialog-content> 34 <md-dialog-content>
32 <div class="md-dialog-content"> 35 <div class="md-dialog-content">
33 <md-content class="md-padding" layout="column"> 36 <md-content class="md-padding" layout="column">
34 <fieldset ng-disabled="loading"> 37 <fieldset ng-disabled="loading">
35 <section flex layout="row"> 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 <label translate>extension.extension-id</label> 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 <div ng-messages="theForm.extensionId.$error"> 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 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div> 44 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
42 </div> 45 </div>
43 </md-input-container> 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 <label translate>extension.extension-type</label> 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 <md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value"> 52 <md-option ng-repeat="(key,value) in vm.types.extensionType" ng-value="value">
48 {{value}} 53 {{value}}
49 </md-option> 54 </md-option>
50 </md-select> 55 </md-select>
  56 +
51 <div ng-messages="theForm.extensionType.$error"> 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 </div> 59 </div>
54 </md-input-container> 60 </md-input-container>
55 </section> 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 </fieldset> 65 </fieldset>
61 -  
62 - <!--<div>{{vm.newExtension}}</div>-->  
63 </md-content> 66 </md-content>
64 </div> 67 </div>
65 </md-dialog-content> 68 </md-dialog-content>
  69 +
66 <md-dialog-actions layout="row"> 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 {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} 74 {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
71 </md-button> 75 </md-button>
  76 +
72 <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }} 77 <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}
73 </md-button> 78 </md-button>
74 </md-dialog-actions> 79 </md-dialog-actions>
@@ -126,11 +126,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, @@ -126,11 +126,13 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
126 controllerAs: 'vm', 126 controllerAs: 'vm',
127 templateUrl: extensionDialogTemplate, 127 templateUrl: extensionDialogTemplate,
128 parent: angular.element($document[0].body), 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 bindToController: true, 136 bindToController: true,
135 targetEvent: $event, 137 targetEvent: $event,
136 fullscreen: true, 138 fullscreen: true,
@@ -53,74 +53,62 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -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 scope.addConverterConfig = function() { 57 scope.addConverterConfig = function() {
83 var newConverterConfig = {converterId:"", converters:[]}; 58 var newConverterConfig = {converterId:"", converters:[]};
84 scope.converterConfigs.push(newConverterConfig); 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 scope.removeConverterConfig = function(config) { 65 scope.removeConverterConfig = function(config) {
88 var index = scope.converterConfigs.indexOf(config); 66 var index = scope.converterConfigs.indexOf(config);
89 if (index > -1) { 67 if (index > -1) {
90 scope.converterConfigs.splice(index, 1); 68 scope.converterConfigs.splice(index, 1);
91 } 69 }
92 - scope.theForm.$setDirty();  
93 - } 70 + };
94 71
95 scope.addConverter = function(converters) { 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 converters.push(newConverter); 79 converters.push(newConverter);
98 - } 80 + };
99 81
100 scope.removeConverter = function(converter, converters) { 82 scope.removeConverter = function(converter, converters) {
101 var index = converters.indexOf(converter); 83 var index = converters.indexOf(converter);
102 if (index > -1) { 84 if (index > -1) {
103 converters.splice(index, 1); 85 converters.splice(index, 1);
104 } 86 }
105 - scope.theForm.$setDirty();  
106 - } 87 + };
107 88
108 scope.addAttribute = function(attributes) { 89 scope.addAttribute = function(attributes) {
109 var newAttribute = {type:"", key:"", value:""}; 90 var newAttribute = {type:"", key:"", value:""};
110 attributes.push(newAttribute); 91 attributes.push(newAttribute);
111 - } 92 + };
112 93
113 scope.removeAttribute = function(attribute, attributes) { 94 scope.removeAttribute = function(attribute, attributes) {
114 var index = attributes.indexOf(attribute); 95 var index = attributes.indexOf(attribute);
115 if (index > -1) { 96 if (index > -1) {
116 attributes.splice(index, 1); 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 scope.transformerTypeChange = function(attribute) { 109 scope.transformerTypeChange = function(attribute) {
122 attribute.transformer = ""; 110 attribute.transformer = "";
123 - } 111 + };
124 112
125 scope.validateTransformer = function (model, editorName) { 113 scope.validateTransformer = function (model, editorName) {
126 if(model && model.length) { 114 if(model && model.length) {
@@ -131,10 +119,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -131,10 +119,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
131 scope.theForm[editorName].$setValidity('transformerJSON', false); 119 scope.theForm[editorName].$setValidity('transformerJSON', false);
132 } 120 }
133 } 121 }
134 - }  
135 - 122 + };
  123 +
136 $compile(element.contents())(scope); 124 $compile(element.contents())(scope);
137 - } 125 + };
138 126
139 return { 127 return {
140 restrict: "A", 128 restrict: "A",
@@ -23,18 +23,19 @@ @@ -23,18 +23,19 @@
23 </md-card-title> 23 </md-card-title>
24 <md-card-content> 24 <md-card-content>
25 <v-accordion id="http-converter-configs-accordion" class="vAccordion--default"> 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 <v-pane-header> 27 <v-pane-header>
28 {{ 'extension.converter-configurations' | translate }} 28 {{ 'extension.converter-configurations' | translate }}
29 </v-pane-header> 29 </v-pane-header>
30 <v-pane-content> 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 <div ng-if="converterConfigs.length > 0"> 31 <div ng-if="converterConfigs.length > 0">
35 <ol class="list-group"> 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 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> 39 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
39 <md-tooltip md-direction="top"> 40 <md-tooltip md-direction="top">
40 {{ 'action.remove' | translate }} 41 {{ 'action.remove' | translate }}
@@ -43,11 +44,11 @@ @@ -43,11 +44,11 @@
43 <md-card> 44 <md-card>
44 <md-card-content> 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 <label translate>extension.converter-id</label> 48 <label translate>extension.converter-id</label>
48 <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId"> 49 <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
49 <div ng-messages="theForm['httpConverterId_' + configIndex].$error"> 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 </div> 52 </div>
52 </md-input-container> 53 </md-input-container>
53 <md-input-container class="md-block"> 54 <md-input-container class="md-block">
@@ -55,18 +56,21 @@ @@ -55,18 +56,21 @@
55 <input name="httpToken" ng-model="config.token" parse-to-null> 56 <input name="httpToken" ng-model="config.token" parse-to-null>
56 </md-input-container> 57 </md-input-container>
57 <v-accordion id="http-converters-accordion" class="vAccordion--default"> 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 <v-pane-header> 60 <v-pane-header>
60 {{ 'extension.converters' | translate }} 61 {{ 'extension.converters' | translate }}
61 </v-pane-header> 62 </v-pane-header>
62 <v-pane-content> 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 <div ng-if="config.converters.length > 0"> 64 <div ng-if="config.converters.length > 0">
67 <ol class="list-group"> 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 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> 74 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
71 <md-tooltip md-direction="top"> 75 <md-tooltip md-direction="top">
72 {{ 'action.remove' | translate }} 76 {{ 'action.remove' | translate }}
@@ -74,18 +78,18 @@ @@ -74,18 +78,18 @@
74 </md-button> 78 </md-button>
75 <md-card> 79 <md-card>
76 <md-card-content> 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 <label translate>extension.device-name-expression</label> 82 <label translate>extension.device-name-expression</label>
79 <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression"> 83 <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
80 <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error"> 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 </div> 86 </div>
83 </md-input-container> 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 <label translate>extension.device-type-expression</label> 89 <label translate>extension.device-type-expression</label>
86 <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression"> 90 <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
87 <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error"> 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 </div> 93 </div>
90 </md-input-container> 94 </md-input-container>
91 95
@@ -107,14 +111,14 @@ @@ -107,14 +111,14 @@
107 <md-card> 111 <md-card>
108 <md-card-content> 112 <md-card-content>
109 <section flex layout="row"> 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 <label translate>extension.key</label> 115 <label translate>extension.key</label>
112 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key"> 116 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
113 <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error"> 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 </div> 119 </div>
116 </md-input-container> 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 <label translate>extension.type</label> 122 <label translate>extension.type</label>
119 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type"> 123 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
120 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> 124 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -122,16 +126,16 @@ @@ -122,16 +126,16 @@
122 </md-option> 126 </md-option>
123 </md-select> 127 </md-select>
124 <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error"> 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 </div> 130 </div>
127 </md-input-container> 131 </md-input-container>
128 </section> 132 </section>
129 <section flex layout="row"> 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 <label translate>extension.value</label> 135 <label translate>extension.value</label>
132 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value"> 136 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
133 <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error"> 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 </div> 139 </div>
136 </md-input-container> 140 </md-input-container>
137 141
@@ -172,11 +176,8 @@ @@ -172,11 +176,8 @@
172 <div flex layout="row" layout-align="start center"> 176 <div flex layout="row" layout-align="start center">
173 <md-button class="md-primary md-raised" 177 <md-button class="md-primary md-raised"
174 ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 179 <md-icon class="material-icons">add</md-icon>
179 - <span translate>action.add</span> 180 + <span translate>extension.add-attribute</span>
180 </md-button> 181 </md-button>
181 </div> 182 </div>
182 </v-pane-content> 183 </v-pane-content>
@@ -202,14 +203,14 @@ @@ -202,14 +203,14 @@
202 <md-card> 203 <md-card>
203 <md-card-content> 204 <md-card-content>
204 <section flex layout="row"> 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 <label translate>extension.key</label> 207 <label translate>extension.key</label>
207 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key"> 208 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
208 <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error"> 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 </div> 211 </div>
211 </md-input-container> 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 <label translate>extension.type</label> 214 <label translate>extension.type</label>
214 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type"> 215 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
215 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> 216 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -217,16 +218,16 @@ @@ -217,16 +218,16 @@
217 </md-option> 218 </md-option>
218 </md-select> 219 </md-select>
219 <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error"> 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 </div> 222 </div>
222 </md-input-container> 223 </md-input-container>
223 </section> 224 </section>
224 <section flex layout="row"> 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 <label translate>extension.value</label> 227 <label translate>extension.value</label>
227 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> 228 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
228 <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error"> 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 </div> 231 </div>
231 </md-input-container> 232 </md-input-container>
232 233
@@ -267,11 +268,8 @@ @@ -267,11 +268,8 @@
267 <div flex layout="row" layout-align="start center"> 268 <div flex layout="row" layout-align="start center">
268 <md-button class="md-primary md-raised" 269 <md-button class="md-primary md-raised"
269 ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 271 <md-icon class="material-icons">add</md-icon>
274 - <span translate>action.add</span> 272 + <span translate>extension.add-timeseries</span>
275 </md-button> 273 </md-button>
276 </div> 274 </div>
277 </v-pane-content> 275 </v-pane-content>
@@ -285,11 +283,8 @@ @@ -285,11 +283,8 @@
285 <div flex layout="row" layout-align="start center"> 283 <div flex layout="row" layout-align="start center">
286 <md-button class="md-primary md-raised" 284 <md-button class="md-primary md-raised"
287 ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 286 <md-icon class="material-icons">add</md-icon>
292 - <span translate>action.add</span> 287 + <span translate>extension.add-converter</span>
293 </md-button> 288 </md-button>
294 </div> 289 </div>
295 </v-pane-content> 290 </v-pane-content>
@@ -304,11 +299,8 @@ @@ -304,11 +299,8 @@
304 <div flex layout="row" layout-align="start center"> 299 <div flex layout="row" layout-align="start center">
305 <md-button class="md-primary md-raised" 300 <md-button class="md-primary md-raised"
306 ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}"> 301 ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
307 - <md-tooltip md-direction="top">  
308 - {{ 'extension.add-config' | translate }}  
309 - </md-tooltip>  
310 <md-icon class="material-icons">add</md-icon> 302 <md-icon class="material-icons">add</md-icon>
311 - <span translate>action.add</span> 303 + <span translate>extension.add-config</span>
312 </md-button> 304 </md-button>
313 </div> 305 </div>
314 </v-pane-content> 306 </v-pane-content>
@@ -33,14 +33,22 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -33,14 +33,22 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
33 scope.types = types; 33 scope.types = types;
34 scope.theForm = scope.$parent.theForm; 34 scope.theForm = scope.$parent.theForm;
35 35
36 - scope.nameExpressions = { 36 + scope.deviceNameExpressions = {
37 deviceNameJsonExpression: "extension.converter-json", 37 deviceNameJsonExpression: "extension.converter-json",
38 deviceNameTopicExpression: "extension.topic" 38 deviceNameTopicExpression: "extension.topic"
39 }; 39 };
40 - scope.typeExpressions = { 40 + scope.deviceTypeExpressions = {
41 deviceTypeJsonExpression: "extension.converter-json", 41 deviceTypeJsonExpression: "extension.converter-json",
42 deviceTypeTopicExpression: "extension.topic" 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 scope.extensionCustomConverterOptions = { 53 scope.extensionCustomConverterOptions = {
46 useWrapMode: false, 54 useWrapMode: false,
@@ -58,17 +66,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -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 scope.updateValidity = function () { 69 scope.updateValidity = function () {
70 - var valid = scope.brokers && scope.brokers.length > 0;  
71 - scope.theForm.$setValidity('brokers', valid);  
72 if(scope.brokers.length) { 70 if(scope.brokers.length) {
73 for(let i=0;i<scope.brokers.length;i++) { 71 for(let i=0;i<scope.brokers.length;i++) {
74 if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) { 72 if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) {
@@ -81,57 +79,116 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -81,57 +79,116 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
81 } 79 }
82 } 80 }
83 } 81 }
84 - } 82 + };
85 83
86 scope.$watch('brokers', function() { 84 scope.$watch('brokers', function() {
87 scope.updateValidity(); 85 scope.updateValidity();
88 }, true); 86 }, true);
89 87
90 scope.addBroker = function() { 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 scope.brokers.push(newBroker); 102 scope.brokers.push(newBroker);
93 - } 103 + };
94 104
95 scope.removeBroker = function(broker) { 105 scope.removeBroker = function(broker) {
96 var index = scope.brokers.indexOf(broker); 106 var index = scope.brokers.indexOf(broker);
97 if (index > -1) { 107 if (index > -1) {
98 scope.brokers.splice(index, 1); 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 scope.addMap = function(mapping) { 120 scope.addMap = function(mapping) {
104 var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}}; 121 var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}};
105 122
106 mapping.push(newMap); 123 mapping.push(newMap);
107 - } 124 + };
108 125
109 scope.removeMap = function(map, mapping) { 126 scope.removeMap = function(map, mapping) {
110 var index = mapping.indexOf(map); 127 var index = mapping.indexOf(map);
111 if (index > -1) { 128 if (index > -1) {
112 mapping.splice(index, 1); 129 mapping.splice(index, 1);
113 } 130 }
114 - scope.theForm.$setDirty();  
115 - } 131 + };
116 132
117 scope.addAttribute = function(attributes) { 133 scope.addAttribute = function(attributes) {
118 var newAttribute = {type:"", key:"", value:""}; 134 var newAttribute = {type:"", key:"", value:""};
119 attributes.push(newAttribute); 135 attributes.push(newAttribute);
120 - } 136 + };
121 137
122 scope.removeAttribute = function(attribute, attributes) { 138 scope.removeAttribute = function(attribute, attributes) {
123 var index = attributes.indexOf(attribute); 139 var index = attributes.indexOf(attribute);
124 if (index > -1) { 140 if (index > -1) {
125 attributes.splice(index, 1); 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 scope.changeCredentials = function(broker) { 187 scope.changeCredentials = function(broker) {
131 var type = broker.credentials.type; 188 var type = broker.credentials.type;
132 broker.credentials = {}; 189 broker.credentials = {};
133 broker.credentials.type = type; 190 broker.credentials.type = type;
134 - } 191 + };
135 192
136 scope.changeConverterType = function(map) { 193 scope.changeConverterType = function(map) {
137 if(map.converterType == "custom"){ 194 if(map.converterType == "custom"){
@@ -140,20 +197,32 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -140,20 +197,32 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
140 if(map.converterType == "json") { 197 if(map.converterType == "json") {
141 map.converter = {attributes:[],timeseries:[]}; 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 scope.changeTypeExpression = function(converter) { 227 scope.changeTypeExpression = function(converter) {
159 if(converter.typeExp == "deviceTypeJsonExpression") { 228 if(converter.typeExp == "deviceTypeJsonExpression") {
@@ -166,7 +235,37 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -166,7 +235,37 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
166 delete converter.deviceTypeJsonExpression; 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 scope.validateCustomConverter = function(model, editorName) { 270 scope.validateCustomConverter = function(model, editorName) {
172 if(model && model.length) { 271 if(model && model.length) {
@@ -177,7 +276,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -177,7 +276,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
177 scope.theForm[editorName].$setValidity('converterJSON', false); 276 scope.theForm[editorName].$setValidity('converterJSON', false);
178 } 277 }
179 } 278 }
180 - } 279 + };
181 280
182 scope.fileAdded = function($file, broker, fileType) { 281 scope.fileAdded = function($file, broker, fileType) {
183 var reader = new FileReader(); 282 var reader = new FileReader();
@@ -204,7 +303,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -204,7 +303,7 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
204 }); 303 });
205 }; 304 };
206 reader.readAsDataURL($file.file); 305 reader.readAsDataURL($file.file);
207 - } 306 + };
208 307
209 scope.clearFile = function(broker, fileType) { 308 scope.clearFile = function(broker, fileType) {
210 scope.theForm.$setDirty(); 309 scope.theForm.$setDirty();
@@ -220,10 +319,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -220,10 +319,10 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
220 broker.credentials.certFileName = null; 319 broker.credentials.certFileName = null;
221 broker.credentials.cert = null; 320 broker.credentials.cert = null;
222 } 321 }
223 - } 322 + };
224 323
225 $compile(element.contents())(scope); 324 $compile(element.contents())(scope);
226 - } 325 + };
227 326
228 return { 327 return {
229 restrict: "A", 328 restrict: "A",
@@ -23,18 +23,15 @@ @@ -23,18 +23,15 @@
23 </md-card-title> 23 </md-card-title>
24 <md-card-content> 24 <md-card-content>
25 <v-accordion id="mqtt-brokers-accordion" class="vAccordion--default"> 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 <v-pane-header> 27 <v-pane-header>
28 {{ 'extension.brokers' | translate }} 28 {{ 'extension.brokers' | translate }}
29 </v-pane-header> 29 </v-pane-header>
30 <v-pane-content> 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 <div ng-if="brokers.length > 0"> 31 <div ng-if="brokers.length > 0">
35 <ol class="list-group"> 32 <ol class="list-group">
36 <li class="list-group-item" ng-repeat="(brokerIndex,broker) in brokers"> 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 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon> 35 <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
39 <md-tooltip md-direction="top"> 36 <md-tooltip md-direction="top">
40 {{ 'action.remove' | translate }} 37 {{ 'action.remove' | translate }}
@@ -47,7 +44,7 @@ @@ -47,7 +44,7 @@
47 <label translate>extension.port</label> 44 <label translate>extension.port</label>
48 <input required type="number" min="1" max="65535" name="mqttPort_{{brokerIndex}}" ng-model="broker.port"> 45 <input required type="number" min="1" max="65535" name="mqttPort_{{brokerIndex}}" ng-model="broker.port">
49 <div ng-messages="theForm['mqttPort_' + brokerIndex].$error"> 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 <div translate ng-message="min">extension.port-range</div> 48 <div translate ng-message="min">extension.port-range</div>
52 <div translate ng-message="max">extension.port-range</div> 49 <div translate ng-message="max">extension.port-range</div>
53 </div> 50 </div>
@@ -56,7 +53,7 @@ @@ -56,7 +53,7 @@
56 <label translate>extension.host</label> 53 <label translate>extension.host</label>
57 <input required name="mqttHost_{{brokerIndex}}" ng-model="broker.host"> 54 <input required name="mqttHost_{{brokerIndex}}" ng-model="broker.host">
58 <div ng-messages="theForm['mqttHost_' + brokerIndex].$error"> 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 </div> 57 </div>
61 </md-input-container> 58 </md-input-container>
62 </section> 59 </section>
@@ -65,7 +62,7 @@ @@ -65,7 +62,7 @@
65 <label translate>extension.retry-interval</label> 62 <label translate>extension.retry-interval</label>
66 <input required type="number" name="mqttRetryInterval_{{brokerIndex}}" ng-model="broker.retryInterval"> 63 <input required type="number" name="mqttRetryInterval_{{brokerIndex}}" ng-model="broker.retryInterval">
67 <div ng-messages="theForm['mqttRetryInterval_' + brokerIndex].$error"> 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 </div> 66 </div>
70 </md-input-container> 67 </md-input-container>
71 <md-input-container flex="50" class="md-block"> 68 <md-input-container flex="50" class="md-block">
@@ -76,30 +73,30 @@ @@ -76,30 +73,30 @@
76 </md-option> 73 </md-option>
77 </md-select> 74 </md-select>
78 </md-input-container> 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 <md-checkbox flex aria-label="{{ 'extension.ssl' | translate }}" 77 <md-checkbox flex aria-label="{{ 'extension.ssl' | translate }}"
81 ng-model="broker.ssl">{{ 'extension.ssl' | translate }} 78 ng-model="broker.ssl">{{ 'extension.ssl' | translate }}
82 </md-checkbox> 79 </md-checkbox>
83 </md-input-container> 80 </md-input-container>
84 </section> 81 </section>
85 <section flex layout="row" ng-if='broker.credentials.type == "basic"'> 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 <label translate>extension.username</label> 84 <label translate>extension.username</label>
88 <input required name="mqttUsername_{{brokerIndex}}" ng-model="broker.credentials.username"> 85 <input required name="mqttUsername_{{brokerIndex}}" ng-model="broker.credentials.username">
89 <div ng-messages="theForm['mqttUsername_' + brokerIndex].$error"> 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 </div> 88 </div>
92 </md-input-container> 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 <label translate>extension.password</label> 91 <label translate>extension.password</label>
95 <input required name="mqttPassword_{{brokerIndex}}" ng-model="broker.credentials.password"> 92 <input required name="mqttPassword_{{brokerIndex}}" ng-model="broker.credentials.password">
96 <div ng-messages="theForm['mqttPassword_' + brokerIndex].$error"> 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 </div> 95 </div>
99 </md-input-container> 96 </md-input-container>
100 </section> 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 <label class="tb-label" translate>extension.ca-cert</label> 100 <label class="tb-label" translate>extension.ca-cert</label>
104 <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "caCert")' class="tb-file-select-container"> 101 <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "caCert")' class="tb-file-select-container">
105 <div class="tb-file-clear-container"> 102 <div class="tb-file-clear-container">
@@ -120,7 +117,7 @@ @@ -120,7 +117,7 @@
120 <div ng-if="!broker.credentials.caCertFileName" class="tb-error-message" translate>extension.no-file</div> 117 <div ng-if="!broker.credentials.caCertFileName" class="tb-error-message" translate>extension.no-file</div>
121 <div ng-if="broker.credentials.caCertFileName">{{broker.credentials.caCertFileName}}</div> 118 <div ng-if="broker.credentials.caCertFileName">{{broker.credentials.caCertFileName}}</div>
122 </div> 119 </div>
123 - <div class="tb-container"> 120 + <div class="tb-container" ng-class="broker.credentials.privateKeyFileName ? 'ng-valid' : 'ng-invalid'">
124 <label class="tb-label" translate>extension.private-key</label> 121 <label class="tb-label" translate>extension.private-key</label>
125 <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "privateKey")' class="tb-file-select-container"> 122 <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "privateKey")' class="tb-file-select-container">
126 <div class="tb-file-clear-container"> 123 <div class="tb-file-clear-container">
@@ -182,7 +179,7 @@ @@ -182,7 +179,7 @@
182 <md-card> 179 <md-card>
183 <md-card-content> 180 <md-card-content>
184 <section flex layout="row"> 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 <label translate>extension.converter-type</label> 183 <label translate>extension.converter-type</label>
187 <md-select required name="mqttConverterType_{{brokerIndex}}{{mapIndex}}" ng-model="map.converterType" ng-change="changeConverterType(map)"> 184 <md-select required name="mqttConverterType_{{brokerIndex}}{{mapIndex}}" ng-model="map.converterType" ng-change="changeConverterType(map)">
188 <md-option ng-repeat="(converterType, value) in types.mqttConverterTypes" ng-value="converterType"> 185 <md-option ng-repeat="(converterType, value) in types.mqttConverterTypes" ng-value="converterType">
@@ -190,64 +187,70 @@ @@ -190,64 +187,70 @@
190 </md-option> 187 </md-option>
191 </md-select> 188 </md-select>
192 <div ng-messages="theForm['mqttConverterType_' + brokerIndex + mapIndex].$error"> 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 </div> 191 </div>
195 </md-input-container> 192 </md-input-container>
196 <md-input-container flex="60" class="md-block"> 193 <md-input-container flex="60" class="md-block">
197 <label translate>extension.topic-filter</label> 194 <label translate>extension.topic-filter</label>
198 <input required name="mqttTopicFilter_{{brokerIndex}}{{mapIndex}}" ng-model="map.topicFilter"> 195 <input required name="mqttTopicFilter_{{brokerIndex}}{{mapIndex}}" ng-model="map.topicFilter">
199 <div ng-messages="theForm['mqttTopicFilter_' + brokerIndex + mapIndex].$error"> 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 </div> 198 </div>
202 </md-input-container> 199 </md-input-container>
203 </section> 200 </section>
204 201
205 <div ng-if='map.converterType =="json"' ng-init="map.converter.type = 'json'"> 202 <div ng-if='map.converterType =="json"' ng-init="map.converter.type = 'json'">
206 <section flex layout="row"> 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 <md-select required name="mqttDeviceNameExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.nameExp" ng-change="changeNameExpression(map.converter)"> 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 {{value | translate}} 208 {{value | translate}}
212 </md-option> 209 </md-option>
213 </md-select> 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 </md-input-container> 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 <label translate>extension.json-name-expression</label> 216 <label translate>extension.json-name-expression</label>
217 <input required name="mqttJsonNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameJsonExpression"> 217 <input required name="mqttJsonNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameJsonExpression">
218 <div ng-messages="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$error"> 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 </div> 220 </div>
221 </md-input-container> 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 <label translate>extension.topic-name-expression</label> 223 <label translate>extension.topic-name-expression</label>
224 <input required name="mqttTopicNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameTopicExpression"> 224 <input required name="mqttTopicNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameTopicExpression">
225 <div ng-messages="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$error"> 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 </div> 227 </div>
228 </md-input-container> 228 </md-input-container>
229 </section> 229 </section>
230 <section flex layout="row"> 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 <md-select required name="mqttDeviceTypeExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.typeExp" ng-change="changeTypeExpression(map.converter)"> 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 {{value | translate}} 235 {{value | translate}}
236 </md-option> 236 </md-option>
237 </md-select> 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 </md-input-container> 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 <label translate>extension.json-type-expression</label> 243 <label translate>extension.json-type-expression</label>
241 <input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression"> 244 <input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression">
242 <div ng-messages="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$error"> 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 </div> 247 </div>
245 </md-input-container> 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 <label translate>extension.topic-type-expression</label> 250 <label translate>extension.topic-type-expression</label>
248 <input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression"> 251 <input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression">
249 <div ng-messages="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$error"> 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 </div> 254 </div>
252 </md-input-container> 255 </md-input-container>
253 </section> 256 </section>
@@ -256,11 +259,11 @@ @@ -256,11 +259,11 @@
256 <label translate>extension.timeout</label> 259 <label translate>extension.timeout</label>
257 <input type="number" name="mqttTimeout_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.timeout" parse-to-null> 260 <input type="number" name="mqttTimeout_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.timeout" parse-to-null>
258 </md-input-container> 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 <label translate>extension.filter-expression</label> 263 <label translate>extension.filter-expression</label>
261 <input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression"> 264 <input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
262 <div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error"> 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 </div> 267 </div>
265 </md-input-container> 268 </md-input-container>
266 </section> 269 </section>
@@ -301,14 +304,14 @@ @@ -301,14 +304,14 @@
301 <md-card> 304 <md-card>
302 <md-card-content> 305 <md-card-content>
303 <section flex layout="row"> 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 <label translate>extension.key</label> 308 <label translate>extension.key</label>
306 <input required name="mqttAttributeKey_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.key"> 309 <input required name="mqttAttributeKey_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.key">
307 <div ng-messages="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$error"> 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 </div> 312 </div>
310 </md-input-container> 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 <label translate>extension.type</label> 315 <label translate>extension.type</label>
313 <md-select required name="mqttAttributeType_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.type"> 316 <md-select required name="mqttAttributeType_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.type">
314 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> 317 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -316,15 +319,15 @@ @@ -316,15 +319,15 @@
316 </md-option> 319 </md-option>
317 </md-select> 320 </md-select>
318 <div ng-messages="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$error"> 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 </div> 323 </div>
321 </md-input-container> 324 </md-input-container>
322 </section> 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 <label translate>extension.value</label> 327 <label translate>extension.value</label>
325 <input required name="mqttAttributeValue_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.value"> 328 <input required name="mqttAttributeValue_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.value">
326 <div ng-messages="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$error"> 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 </div> 331 </div>
329 </md-input-container> 332 </md-input-container>
330 </md-card-content> 333 </md-card-content>
@@ -335,11 +338,8 @@ @@ -335,11 +338,8 @@
335 <div flex layout="row" layout-align="start center"> 338 <div flex layout="row" layout-align="start center">
336 <md-button class="md-primary md-raised" 339 <md-button class="md-primary md-raised"
337 ng-click="addAttribute(map.converter.attributes)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 341 <md-icon class="material-icons">add</md-icon>
342 - <span translate>action.add</span> 342 + <span translate>extension.add-attribute</span>
343 </md-button> 343 </md-button>
344 </div> 344 </div>
345 </v-pane-content> 345 </v-pane-content>
@@ -364,14 +364,14 @@ @@ -364,14 +364,14 @@
364 <md-card> 364 <md-card>
365 <md-card-content> 365 <md-card-content>
366 <section flex layout="row"> 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 <label translate>extension.key</label> 368 <label translate>extension.key</label>
369 <input required name="mqttTimeseriesKey_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.key"> 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 </div> 372 </div>
373 </md-input-container> 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 <label translate>extension.type</label> 375 <label translate>extension.type</label>
376 <md-select required name="mqttTimeseriesType_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.type"> 376 <md-select required name="mqttTimeseriesType_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
377 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> 377 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -379,15 +379,15 @@ @@ -379,15 +379,15 @@
379 </md-option> 379 </md-option>
380 </md-select> 380 </md-select>
381 <div ng-messages="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$error"> 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 </div> 383 </div>
384 </md-input-container> 384 </md-input-container>
385 </section> 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 <label translate>extension.value</label> 387 <label translate>extension.value</label>
388 <input required name="mqttTimeseriesValue_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> 388 <input required name="mqttTimeseriesValue_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
389 <div ng-messages="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$error"> 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 </div> 391 </div>
392 </md-input-container> 392 </md-input-container>
393 </md-card-content> 393 </md-card-content>
@@ -398,11 +398,8 @@ @@ -398,11 +398,8 @@
398 <div flex layout="row" layout-align="start center"> 398 <div flex layout="row" layout-align="start center">
399 <md-button class="md-primary md-raised" 399 <md-button class="md-primary md-raised"
400 ng-click="addAttribute(map.converter.timeseries)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 401 <md-icon class="material-icons">add</md-icon>
405 - <span translate>action.add</span> 402 + <span translate>extension.add-timeseries</span>
406 </md-button> 403 </md-button>
407 </div> 404 </div>
408 </v-pane-content> 405 </v-pane-content>
@@ -417,16 +414,429 @@ @@ -417,16 +414,429 @@
417 <div flex layout="row" layout-align="start center"> 414 <div flex layout="row" layout-align="start center">
418 <md-button class="md-primary md-raised" 415 <md-button class="md-primary md-raised"
419 ng-click="addMap(broker.mapping)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 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 </md-button> 553 </md-button>
426 </div> 554 </div>
427 </v-pane-content> 555 </v-pane-content>
428 </v-pane> 556 </v-pane>
429 </v-accordion> 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 </md-card-content> 840 </md-card-content>
431 </md-card> 841 </md-card>
432 </li> 842 </li>
@@ -436,18 +846,15 @@ @@ -436,18 +846,15 @@
436 <div flex layout="row" layout-align="start center"> 846 <div flex layout="row" layout-align="start center">
437 <md-button class="md-primary md-raised" 847 <md-button class="md-primary md-raised"
438 ng-click="addBroker()" aria-label="{{ 'action.add' | translate }}"> 848 ng-click="addBroker()" aria-label="{{ 'action.add' | translate }}">
439 - <md-tooltip md-direction="top">  
440 - {{ 'extension.add-broker' | translate }}  
441 - </md-tooltip>  
442 <md-icon class="material-icons">add</md-icon> 849 <md-icon class="material-icons">add</md-icon>
443 - <span translate>action.add</span> 850 + <span translate>extension.add-broker</span>
444 </md-button> 851 </md-button>
445 </div> 852 </div>
446 </v-pane-content> 853 </v-pane-content>
447 </v-pane> 854 </v-pane>
448 </v-accordion> 855 </v-accordion>
449 -<pre> 856 +<!--<pre>
450 {{config | json}} 857 {{config | json}}
451 -</pre> 858 +</pre>-->
452 </md-card-content> 859 </md-card-content>
453 </md-card> 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 +}
@@ -15,4 +15,543 @@ @@ -15,4 +15,543 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div>OPC UA</div>  
  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>
@@ -22,9 +22,6 @@ @@ -22,9 +22,6 @@
22 margin-top: 0; 22 margin-top: 0;
23 padding-left: 3px; 23 padding-left: 3px;
24 } 24 }
25 - .t-right {  
26 - text-align: right;  
27 - }  
28 .tb-container { 25 .tb-container {
29 width:100%; 26 width:100%;
30 } 27 }
@@ -33,6 +30,15 @@ @@ -33,6 +30,15 @@
33 padding: 5px 0 0 0; 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 .tb-extension-custom-transformer-panel { 44 .tb-extension-custom-transformer-panel {
@@ -48,4 +54,20 @@ @@ -48,4 +54,20 @@
48 .ace_text-input { 54 .ace_text-input {
49 position:absolute!important 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 }
@@ -17,11 +17,13 @@ @@ -17,11 +17,13 @@
17 import ExtensionTableDirective from './extension-table.directive'; 17 import ExtensionTableDirective from './extension-table.directive';
18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive'; 18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
19 import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive' 19 import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
  20 +import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
20 import {ParseToNull} from './extension-dialog.controller'; 21 import {ParseToNull} from './extension-dialog.controller';
21 22
22 export default angular.module('thingsboard.extension', []) 23 export default angular.module('thingsboard.extension', [])
23 .directive('tbExtensionTable', ExtensionTableDirective) 24 .directive('tbExtensionTable', ExtensionTableDirective)
24 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective) 25 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
25 .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective) 26 .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
  27 + .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
26 .directive('parseToNull', ParseToNull) 28 .directive('parseToNull', ParseToNull)
27 .name; 29 .name;
@@ -739,12 +739,7 @@ export default angular.module('thingsboard.locale', []) @@ -739,12 +739,7 @@ export default angular.module('thingsboard.locale', [])
739 "extension-id": "Extension id", 739 "extension-id": "Extension id",
740 "extension-type": "Extension type", 740 "extension-type": "Extension type",
741 "transformer-json": "JSON *", 741 "transformer-json": "JSON *",
742 - "id-required": "Extension id is required.",  
743 "unique-id-required": "Current extension id already exists.", 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 "delete": "Delete extension", 743 "delete": "Delete extension",
749 "add": "Add extension", 744 "add": "Add extension",
750 "edit": "Edit extension", 745 "edit": "Edit extension",
@@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', []) @@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', [])
754 "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", 749 "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
755 "converters": "Converters", 750 "converters": "Converters",
756 "converter-id": "Converter id", 751 "converter-id": "Converter id",
757 - "converter-id-required": "Converter id is required.",  
758 "configuration": "Configuration", 752 "configuration": "Configuration",
759 "converter-configurations": "Converter configurations", 753 "converter-configurations": "Converter configurations",
760 "token": "Security token", 754 "token": "Security token",
761 "add-converter": "Add converter", 755 "add-converter": "Add converter",
762 - "add-converter-prompt": "Please add converter",  
763 "add-config": "Add converter configuration", 756 "add-config": "Add converter configuration",
764 - "add-config-prompt": "Please add converter configuration",  
765 "device-name-expression": "Device name expression", 757 "device-name-expression": "Device name expression",
766 - "device-name-expression-required": "Device name expression is required.",  
767 "device-type-expression": "Device type expression", 758 "device-type-expression": "Device type expression",
768 - "device-type-expression-required": "Device type expression is required.",  
769 "custom": "Custom", 759 "custom": "Custom",
770 "to-double": "To Double", 760 "to-double": "To Double",
771 "transformer": "Transformer", 761 "transformer": "Transformer",
@@ -773,25 +763,20 @@ export default angular.module('thingsboard.locale', []) @@ -773,25 +763,20 @@ export default angular.module('thingsboard.locale', [])
773 "json-parse": "Unable to parse transformer json.", 763 "json-parse": "Unable to parse transformer json.",
774 "attributes": "Attributes", 764 "attributes": "Attributes",
775 "add-attribute": "Add attribute", 765 "add-attribute": "Add attribute",
  766 + "add-map": "Add mapping element",
776 "timeseries": "Timeseries", 767 "timeseries": "Timeseries",
777 "add-timeseries": "Add timeseries", 768 "add-timeseries": "Add timeseries",
778 - 769 + "field-required": "Field is required",
779 "brokers": "Brokers", 770 "brokers": "Brokers",
780 "add-broker": "Add broker", 771 "add-broker": "Add broker",
781 - "add-broker-prompt": "Please add broker",  
782 "host": "Host", 772 "host": "Host",
783 - "host-required": "Host is required.",  
784 "port": "Port", 773 "port": "Port",
785 - "port-required": "Port is required.",  
786 "port-range": "Port should be in a range from 1 to 65535.", 774 "port-range": "Port should be in a range from 1 to 65535.",
787 "ssl": "Ssl", 775 "ssl": "Ssl",
788 "credentials": "Credentials", 776 "credentials": "Credentials",
789 "username": "Username", 777 "username": "Username",
790 - "username-required": "Username is required.",  
791 "password": "Password", 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 "anonymous": "Anonymous", 780 "anonymous": "Anonymous",
796 "basic": "Basic", 781 "basic": "Basic",
797 "pem": "PEM", 782 "pem": "PEM",
@@ -801,28 +786,59 @@ export default angular.module('thingsboard.locale', []) @@ -801,28 +786,59 @@ export default angular.module('thingsboard.locale', [])
801 "no-file": "No file selected.", 786 "no-file": "No file selected.",
802 "drop-file": "Drop a file or click to select a file to upload.", 787 "drop-file": "Drop a file or click to select a file to upload.",
803 "mapping": "Mapping", 788 "mapping": "Mapping",
804 - "add-map": "Add map",  
805 "topic-filter": "Topic filter", 789 "topic-filter": "Topic filter",
806 - "topic-filter-required": "Topic filter is required.",  
807 "converter-type": "Converter type", 790 "converter-type": "Converter type",
808 - "converter-type-required": "Converter type is required.",  
809 "converter-json": "Json", 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 "topic": "Topic", 804 "topic": "Topic",
821 - "timeout": "Timeout", 805 + "timeout": "Timeout in milliseconds",
822 "converter-json-required": "Converter json is required.", 806 "converter-json-required": "Converter json is required.",
823 "converter-json-parse": "Unable to parse converter json.", 807 "converter-json-parse": "Unable to parse converter json.",
824 "filter-expression": "Filter expression", 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 "fullscreen": { 843 "fullscreen": {
828 "expand": "Expand to fullscreen", 844 "expand": "Expand to fullscreen",