Commit 6c5b33705aa69bdee8688cc79234f698f86408f6

Authored by oleg
2 parents 0098a94d 33451e05

final

... ... @@ -30,6 +30,7 @@
30 30 "angular-material": "1.1.1",
31 31 "angular-material-data-table": "^0.10.9",
32 32 "angular-material-icons": "^0.7.1",
  33 + "angular-material-expansion-panel": "^0.7.2",
33 34 "angular-messages": "1.5.8",
34 35 "angular-route": "1.5.8",
35 36 "angular-sanitize": "1.5.8",
... ...
... ... @@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router';
39 39 import angularJwt from 'angular-jwt';
40 40 import 'angular-drag-and-drop-lists';
41 41 import mdDataTable from 'angular-material-data-table';
  42 +import 'angular-material-expansion-panel';
42 43 import ngTouch from 'angular-touch';
43 44 import 'angular-carousel';
44 45 import 'clipboard';
... ... @@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css';
82 83 import 'mdPickers/dist/mdPickers.min.css';
83 84 import 'angular-hotkeys/build/hotkeys.min.css';
84 85 import 'angular-carousel/dist/angular-carousel.min.css';
  86 +import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
85 87 import '../scss/main.scss';
86 88
87 89 import AppConfig from './app.config';
... ... @@ -103,6 +105,7 @@ angular.module('thingsboard', [
103 105 angularJwt,
104 106 'dndLists',
105 107 mdDataTable,
  108 + 'material.components.expansionPanels',
106 109 ngTouch,
107 110 'angular-carousel',
108 111 'ngclipboard',
... ...
... ... @@ -332,6 +332,24 @@ export default angular.module('thingsboard.types', [])
332 332 toDouble: 'extension.to-double',
333 333 custom: 'extension.custom'
334 334 },
  335 + mqttConverterTypes: {
  336 + json: 'extension.converter-json',
  337 + custom: 'extension.custom'
  338 + },
  339 + mqttCredentialTypes: {
  340 + anonymous: {
  341 + value: "anonymous",
  342 + name: "extension.anonymous"
  343 + },
  344 + basic: {
  345 + value: "basic",
  346 + name: "extension.basic"
  347 + },
  348 + pem: {
  349 + value: "cert.PEM",
  350 + name: "extension.pem"
  351 + }
  352 + },
335 353 extensionOpcSecurityTypes: {
336 354 Basic128Rsa15: "Basic128Rsa15",
337 355 Basic256: "Basic256",
... ... @@ -339,8 +357,8 @@ export default angular.module('thingsboard.types', [])
339 357 None: "None"
340 358 },
341 359 extensionIdentityType: {
342   - anonymous: "anonymous",
343   - username: "username"
  360 + anonymous: "extension.anonymous",
  361 + username: "extension.username"
344 362 },
345 363 extensionKeystoreType: {
346 364 PKCS12: "PKCS12",
... ...
... ... @@ -30,11 +30,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
30 30 vm.allExtensions = allExtensions;
31 31
32 32
33   - if (extension) { // Editing
34   - //vm.configuration = vm.extension.configuration;
  33 + if (extension) {
35 34 vm.extension = angular.copy(extension);
36 35 editTransformers(vm.extension);
37   - } else { // Add new
  36 + } else {
38 37 vm.extension = {};
39 38 }
40 39
... ... @@ -65,8 +64,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
65 64
66 65 vm.save = save;
67 66 function save() {
68   - saveTransformers();
69   -
70 67 let $errorElement = angular.element('[name=theForm]').find('.ng-invalid');
71 68
72 69 if ($errorElement.length) {
... ... @@ -78,11 +75,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
78 75
79 76 if ($errorElementTop !== $mdDialogTop) {
80 77 angular.element('md-dialog-content').animate({
81   - scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 20
  78 + scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 50
82 79 }, 500);
83 80 $errorElement.eq(0).focus();
84 81 }
85   -
86 82 } else {
87 83
88 84 if(vm.isAdd) {
... ... @@ -94,6 +90,9 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
94 90 }
95 91 }
96 92
  93 + $mdDialog.hide();
  94 + saveTransformers();
  95 +
97 96 var editedValue = angular.toJson(vm.allExtensions);
98 97
99 98 attributeService
... ... @@ -104,8 +103,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
104 103 [{key:"configuration", value:editedValue}]
105 104 )
106 105 .then(function success() {
107   - $scope.theForm.$setPristine();
108   - $mdDialog.hide();
109 106 });
110 107
111 108 }
... ... @@ -131,21 +128,60 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
131 128 };
132 129
133 130 function saveTransformers() {
134   - var config = vm.extension.configuration.converterConfigurations;
135 131 if(vm.extension.type == types.extensionType.http) {
136   - for(let i=0;i<config.length;i++) {
137   - for(let j=0;j<config[i].converters.length;j++){
138   - for(let k=0;k<config[i].converters[j].attributes.length;k++){
139   - if(config[i].converters[j].attributes[k].transformerType == "toDouble"){
140   - config[i].converters[j].attributes[k].transformer = {type: "intToDouble"};
  132 + var config = vm.extension.configuration.converterConfigurations;
  133 + if(config && config.length > 0) {
  134 + for(let i=0;i<config.length;i++) {
  135 + for(let j=0;j<config[i].converters.length;j++){
  136 + for(let k=0;k<config[i].converters[j].attributes.length;k++){
  137 + if(config[i].converters[j].attributes[k].transformerType == "toDouble"){
  138 + config[i].converters[j].attributes[k].transformer = {type: "intToDouble"};
  139 + }
  140 + delete config[i].converters[j].attributes[k].transformerType;
  141 + }
  142 + for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
  143 + if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){
  144 + config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"};
  145 + }
  146 + delete config[i].converters[j].timeseries[l].transformerType;
141 147 }
142   - delete config[i].converters[j].attributes[k].transformerType;
143 148 }
144   - for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
145   - if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){
146   - config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"};
  149 + }
  150 + }
  151 + }
  152 + if(vm.extension.type == types.extensionType.mqtt) {
  153 + var brokers = vm.extension.configuration.brokers;
  154 + if(brokers && brokers.length > 0) {
  155 + for(let i=0;i<brokers.length;i++) {
  156 + if(brokers[i].mapping && brokers[i].mapping.length > 0) {
  157 + for(let j=0;j<brokers[i].mapping.length;j++) {
  158 + if(brokers[i].mapping[j].converterType == "json") {
  159 + delete brokers[i].mapping[j].converter.nameExp;
  160 + delete brokers[i].mapping[j].converter.typeExp;
  161 + }
  162 + delete brokers[i].mapping[j].converterType;
  163 + }
  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;
147 184 }
148   - delete config[i].converters[j].timeseries[l].transformerType;
149 185 }
150 186 }
151 187 }
... ... @@ -153,8 +189,8 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
153 189 }
154 190
155 191 function editTransformers(extension) {
156   - var config = extension.configuration.converterConfigurations;
157 192 if(extension.type == types.extensionType.http) {
  193 + var config = extension.configuration.converterConfigurations;
158 194 for(let i=0;i<config.length;i++) {
159 195 for(let j=0;j<config[i].converters.length;j++){
160 196 for(let k=0;k<config[i].converters[j].attributes.length;k++){
... ... @@ -180,6 +216,67 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
180 216 }
181 217 }
182 218 }
  219 + if(extension.type == types.extensionType.mqtt) {
  220 + var brokers = extension.configuration.brokers;
  221 + for(let i=0;i<brokers.length;i++) {
  222 + if(brokers[i].mapping && brokers[i].mapping.length > 0) {
  223 + for(let j=0;j<brokers[i].mapping.length;j++) {
  224 + if(brokers[i].mapping[j].converter.type == "json") {
  225 + if(brokers[i].mapping[j].converter.deviceNameTopicExpression) {
  226 + brokers[i].mapping[j].converter.nameExp = "deviceNameTopicExpression";
  227 + } else {
  228 + brokers[i].mapping[j].converter.nameExp = "deviceNameJsonExpression";
  229 + }
  230 + if(brokers[i].mapping[j].converter.deviceTypeTopicExpression) {
  231 + brokers[i].mapping[j].converter.typeExp = "deviceTypeTopicExpression";
  232 + } else {
  233 + brokers[i].mapping[j].converter.typeExp = "deviceTypeJsonExpression";
  234 + }
  235 + brokers[i].mapping[j].converterType = "json";
  236 + } else {
  237 + brokers[i].mapping[j].converterType = "custom";
  238 + }
  239 + }
  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 + }
  278 + }
  279 + }
183 280 }
184 281 }
185 282
... ...
... ... @@ -36,16 +36,16 @@
36 36 <md-content class="md-padding" layout="column">
37 37 <fieldset ng-disabled="loading">
38 38 <section flex layout="row">
39   - <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">
40 40 <label translate>extension.extension-id</label>
41 41 <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
42 42 <div ng-messages="theForm.extensionId.$error">
43   - <div translate ng-message="required">extension.id-required</div>
  43 + <div translate ng-message="required">extension.field-required</div>
44 44 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
45 45 </div>
46 46 </md-input-container>
47 47
48   - <md-input-container flex="40" class="md-block">
  48 + <md-input-container flex="40" class="md-block" md-is-error="theForm.extensionType.$touched && theForm.extensionType.$invalid">
49 49 <label translate>extension.extension-type</label>
50 50
51 51 <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
... ... @@ -55,16 +55,14 @@
55 55 </md-select>
56 56
57 57 <div ng-messages="theForm.extensionType.$error">
58   - <div translate ng-message="required">extension.type-required</div>
  58 + <div translate ng-message="required">extension.field-required</div>
59 59 </div>
60 60 </md-input-container>
61 61 </section>
62   -
63   - <div tb-extension-form-http config="vm.extension.configuration" is-add="vm.isAdd" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.http"></div>
64   -
65   - <div tb-extension-form-opc configuration="vm.extension.configuration" ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
  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>
66 65 </fieldset>
67   - <!--<div>{{vm.extension}}</div>-->
68 66 </md-content>
69 67 </div>
70 68 </md-dialog-content>
... ... @@ -80,5 +78,4 @@
80 78 </md-button>
81 79 </md-dialog-actions>
82 80 </form>
83   -</md-dialog>
84   -
  81 +</md-dialog>
\ No newline at end of file
... ...
... ... @@ -43,7 +43,7 @@ export default function ExtensionTableDirective() {
43 43 }
44 44
45 45 /*@ngInject*/
46   -function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService) {
  46 +function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService) {
47 47
48 48 let vm = this;
49 49
... ... @@ -83,51 +83,6 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
83 83 }
84 84 });
85 85
86   - $scope.$watch('vm.transferredAttributes', function () {
87   - if (vm.transferredAttributes && vm.transferredAttributes.data && vm.transferredAttributes.data.length) {
88   - vm.transferredAttributes.data
89   - .some(attribute=>{
90   - if (attribute.key === "appliedConfiguration") {
91   - vm.appliedConfiguration = attribute.value;
92   - }
93   - });
94   -
95   - checkForSync();
96   - }
97   - });
98   -
99   -
100   - checkForSync();
101   - function checkForSync() {
102   - if (vm.appliedConfiguration === vm.extensionsJSON) {
103   - vm.syncStatus = $translate.instant('extension.sync.sync');
104   - vm.syncLastTime = formatDate();
105   - } else {
106   - vm.syncStatus = $translate.instant('extension.sync.not-sync');
107   - }
108   - }
109   -
110   -
111   - function formatDate(date) {
112   - let d;
113   - if (date) {
114   - d = date;
115   - } else {
116   - d = new Date();
117   - }
118   -
119   - d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds());
120   - return d;
121   -
122   -
123   - function addZero(num) {
124   - if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) {
125   - num = '0' + num;
126   - }
127   - return num;
128   - }
129   - }
130   -
131 86 function enterFilterMode() {
132 87 vm.query.search = '';
133 88 }
... ... @@ -288,57 +243,73 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
288 243 }
289 244
290 245
291   - // vm.subscriptionId = null;
292   - // $scope.checkSubscription = function() {
293   - // var newSubscriptionId = null;
294   - // if (vm.entityId && vm.entityType) {
295   - // newSubscriptionId = attributeService.subscribeForEntityAttributes(vm.entityType, vm.entityId, 'extension/SHARED_SCOPE');
296   - // }
297   - // if (vm.subscriptionId && vm.subscriptionId != newSubscriptionId) {
298   - // attributeService.unsubscribeForEntityAttributes(vm.subscriptionId);
299   - // }
300   - // vm.subscriptionId = newSubscriptionId;
301   - // }
302   - //
303   - //
304   - // // $scope.attributesData = {};
305   - // // var entityAttributesSubscriptionMap = [];
306   - // //
307   - // $scope.subscribeForEntityAttributes = function (entityType=vm.entityType, entityId=vm.entityId, attributeScope="SHARED_SCOPE") {
308   - // var subscriptionId = entityType + entityId + attributeScope;
309   - // var entityAttributesSubscription = entityAttributesSubscriptionMap[subscriptionId];
310   - // if (!entityAttributesSubscription) {
311   - // var subscriptionCommand = {
312   - // entityType: entityType,
313   - // entityId: entityId,
314   - // scope: attributeScope
315   - // };
316   - //
317   - // var type = attributeScope === types.latestTelemetry.value ?
318   - // types.dataKeyType.timeseries : types.dataKeyType.attribute;
319   - //
320   - // var subscriber = {
321   - // subscriptionCommands: [subscriptionCommand],
322   - // type: type,
323   - // onData: function (data) {
324   - // if (data.data) {
325   - // onSubscriptionData(data.data, subscriptionId);
326   - // }
327   - // }
328   - // };
329   - // entityAttributesSubscription = {
330   - // subscriber: subscriber,
331   - // attributes: null
332   - // };
333   - // entityAttributesSubscriptionMap[subscriptionId] = entityAttributesSubscription;
334   - // telemetryWebsocketService.subscribe(subscriber);
335   - // }
336   - // return subscriptionId;
337   - // };
338   - //
339   - // function onSubscriptionData(data/*, subscriptionId*/) {
340   - // $scope.attributesData = data;
341   - // }
342   -
343   - // telemetryWebsocketService.subscribe(subscriber);
  246 +
  247 + if (vm.entityId && vm.entityType) {
  248 + $scope.subscriber = {
  249 + subscriptionCommands: [{
  250 + entityType: vm.entityType,
  251 + entityId: vm.entityId,
  252 + scope: 'CLIENT_SCOPE'
  253 + }],
  254 + type: 'attribute',
  255 + onData: function (data) {
  256 + if (data.data) {
  257 + onSubscriptionData(data.data/*, subscriptionId*/);
  258 + }
  259 + }
  260 + };
  261 + }
  262 + telemetryWebsocketService.subscribe($scope.subscriber);
  263 +
  264 +
  265 + $scope.$on('$destroy', function() {
  266 + telemetryWebsocketService.unsubscribe($scope.subscriber);
  267 + });
  268 +
  269 +
  270 +
  271 +
  272 + function onSubscriptionData(data/*, subscriptionId*/) {
  273 + //$scope.attributesData = data.;
  274 +
  275 + if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) {
  276 + vm.appliedConfiguration = data.appliedConfiguration[0][1];
  277 + checkForSync();
  278 + $scope.$digest();
  279 + }
  280 + }
  281 +
  282 +
  283 + checkForSync();
  284 + function checkForSync() {
  285 + if (vm.appliedConfiguration === vm.extensionsJSON) {
  286 + vm.syncStatus = $translate.instant('extension.sync.sync');
  287 + vm.syncLastTime = formatDate();
  288 + } else {
  289 + vm.syncStatus = $translate.instant('extension.sync.not-sync');
  290 + }
  291 + }
  292 +
  293 + function formatDate(date) {
  294 + let d;
  295 + if (date) {
  296 + d = date;
  297 + } else {
  298 + d = new Date();
  299 + }
  300 +
  301 + d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds());
  302 + return d;
  303 +
  304 +
  305 + function addZero(num) {
  306 + if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) {
  307 + num = '0' + num;
  308 + }
  309 + return num;
  310 + }
  311 + }
  312 +
  313 +
  314 + //telemetryWebsocketService.subscribe(subscriber);
344 315 }
\ No newline at end of file
... ...
... ... @@ -67,7 +67,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
67 67 if (index > -1) {
68 68 scope.converterConfigs.splice(index, 1);
69 69 }
70   - scope.theForm.$setDirty();
71 70 };
72 71
73 72 scope.addConverter = function(converters) {
... ... @@ -85,7 +84,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
85 84 if (index > -1) {
86 85 converters.splice(index, 1);
87 86 }
88   - scope.theForm.$setDirty();
89 87 };
90 88
91 89 scope.addAttribute = function(attributes) {
... ... @@ -98,13 +96,9 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
98 96 if (index > -1) {
99 97 attributes.splice(index, 1);
100 98 }
101   - scope.theForm.$setDirty();
102 99 };
103 100
104 101
105   -
106   -
107   -
108 102 if(scope.isAdd) {
109 103 scope.converterConfigs = scope.config.converterConfigurations;
110 104 scope.addConverterConfig();
... ... @@ -112,28 +106,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
112 106 scope.converterConfigs = scope.config.converterConfigurations;
113 107 }
114 108
115   -
116   -
117   - scope.updateValidity = function() {
118   - let valid = scope.converterConfigs && scope.converterConfigs.length > 0;
119   - scope.theForm.$setValidity('converterConfigs', valid);
120   - if(scope.converterConfigs.length) {
121   - for(let i=0; i<scope.converterConfigs.length; i++) {
122   - if(!scope.converterConfigs[i].converters.length) {
123   - scope.theForm.$setValidity('converters', false);
124   - break;
125   - } else {
126   - scope.theForm.$setValidity('converters', true);
127   - }
128   - }
129   - }
130   - };
131   -
132   - scope.$watch('converterConfigs', function() {
133   - scope.updateValidity();
134   - }, true);
135   -
136   -
137 109 scope.transformerTypeChange = function(attribute) {
138 110 attribute.transformer = "";
139 111 };
... ...
... ... @@ -23,14 +23,11 @@
23 23 </md-card-title>
24 24 <md-card-content>
25 25 <v-accordion id="http-converter-configs-accordion" class="vAccordion--default">
26   - <v-pane id="http-converters-pane" expanded="isAdd">
  26 + <v-pane id="http-converters-pane" expanded="true">
27 27 <v-pane-header>
28 28 {{ 'extension.converter-configurations' | translate }}
29 29 </v-pane-header>
30 30 <v-pane-content>
31   - <div ng-if="converterConfigs.length === 0">
32   - <span translate layout-align="center center" class="tb-prompt">extension.add-config-prompt</span>
33   - </div>
34 31 <div ng-if="converterConfigs.length > 0">
35 32 <ol class="list-group">
36 33 <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs">
... ... @@ -47,11 +44,11 @@
47 44 <md-card>
48 45 <md-card-content>
49 46
50   - <md-input-container class="md-block">
  47 + <md-input-container class="md-block" md-is-error="theForm['httpConverterId_' + configIndex].$touched && theForm['httpConverterId_' + configIndex].$invalid">
51 48 <label translate>extension.converter-id</label>
52 49 <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
53 50 <div ng-messages="theForm['httpConverterId_' + configIndex].$error">
54   - <div translate ng-message="required">extension.converter-id-required</div>
  51 + <div translate ng-message="required">extension.field-required</div>
55 52 </div>
56 53 </md-input-container>
57 54 <md-input-container class="md-block">
... ... @@ -59,14 +56,11 @@
59 56 <input name="httpToken" ng-model="config.token" parse-to-null>
60 57 </md-input-container>
61 58 <v-accordion id="http-converters-accordion" class="vAccordion--default">
62   - <v-pane id="http-converters-pane">
  59 + <v-pane id="http-converters-pane" expanded="true">
63 60 <v-pane-header>
64 61 {{ 'extension.converters' | translate }}
65 62 </v-pane-header>
66 63 <v-pane-content>
67   - <div ng-if="config.converters.length === 0">
68   - <span translate layout-align="center center" class="tb-prompt">extension.add-converter-prompt</span>
69   - </div>
70 64 <div ng-if="config.converters.length > 0">
71 65 <ol class="list-group">
72 66 <li class="list-group-item"
... ... @@ -84,18 +78,18 @@
84 78 </md-button>
85 79 <md-card>
86 80 <md-card-content>
87   - <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">
88 82 <label translate>extension.device-name-expression</label>
89 83 <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
90 84 <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error">
91   - <div translate ng-message="required">extension.device-name-expression-required</div>
  85 + <div translate ng-message="required">extension.field-required</div>
92 86 </div>
93 87 </md-input-container>
94   - <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">
95 89 <label translate>extension.device-type-expression</label>
96 90 <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
97 91 <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error">
98   - <div translate ng-message="required">extension.device-type-expression-required</div>
  92 + <div translate ng-message="required">extension.field-required</div>
99 93 </div>
100 94 </md-input-container>
101 95
... ... @@ -117,14 +111,14 @@
117 111 <md-card>
118 112 <md-card-content>
119 113 <section flex layout="row">
120   - <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">
121 115 <label translate>extension.key</label>
122 116 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
123 117 <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error">
124   - <div translate ng-message="required">extension.required-key</div>
  118 + <div translate ng-message="required">extension.field-required</div>
125 119 </div>
126 120 </md-input-container>
127   - <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">
128 122 <label translate>extension.type</label>
129 123 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
130 124 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
... ... @@ -132,16 +126,16 @@
132 126 </md-option>
133 127 </md-select>
134 128 <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error">
135   - <div translate ng-message="required">extension.required-type</div>
  129 + <div translate ng-message="required">extension.field-required</div>
136 130 </div>
137 131 </md-input-container>
138 132 </section>
139 133 <section flex layout="row">
140   - <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">
141 135 <label translate>extension.value</label>
142 136 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
143 137 <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error">
144   - <div translate ng-message="required">extension.required-value</div>
  138 + <div translate ng-message="required">extension.field-required</div>
145 139 </div>
146 140 </md-input-container>
147 141
... ... @@ -182,11 +176,8 @@
182 176 <div flex layout="row" layout-align="start center">
183 177 <md-button class="md-primary md-raised"
184 178 ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}">
185   - <md-tooltip md-direction="top">
186   - {{ 'extension.add-attribute' | translate }}
187   - </md-tooltip>
188 179 <md-icon class="material-icons">add</md-icon>
189   - <span translate>action.add</span>
  180 + <span translate>extension.add-attribute</span>
190 181 </md-button>
191 182 </div>
192 183 </v-pane-content>
... ... @@ -212,14 +203,14 @@
212 203 <md-card>
213 204 <md-card-content>
214 205 <section flex layout="row">
215   - <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">
216 207 <label translate>extension.key</label>
217 208 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
218 209 <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error">
219   - <div translate ng-message="required">extension.required-key</div>
  210 + <div translate ng-message="required">extension.field-required</div>
220 211 </div>
221 212 </md-input-container>
222   - <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">
223 214 <label translate>extension.type</label>
224 215 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
225 216 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
... ... @@ -227,16 +218,16 @@
227 218 </md-option>
228 219 </md-select>
229 220 <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error">
230   - <div translate ng-message="required">extension.required-type</div>
  221 + <div translate ng-message="required">extension.field-required</div>
231 222 </div>
232 223 </md-input-container>
233 224 </section>
234 225 <section flex layout="row">
235   - <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">
236 227 <label translate>extension.value</label>
237 228 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
238 229 <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error">
239   - <div translate ng-message="required">extension.required-value</div>
  230 + <div translate ng-message="required">extension.field-required</div>
240 231 </div>
241 232 </md-input-container>
242 233
... ... @@ -277,11 +268,8 @@
277 268 <div flex layout="row" layout-align="start center">
278 269 <md-button class="md-primary md-raised"
279 270 ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
280   - <md-tooltip md-direction="top">
281   - {{ 'extension.add-timeseries' | translate }}
282   - </md-tooltip>
283 271 <md-icon class="material-icons">add</md-icon>
284   - <span translate>action.add</span>
  272 + <span translate>extension.add-timeseries</span>
285 273 </md-button>
286 274 </div>
287 275 </v-pane-content>
... ... @@ -295,11 +283,8 @@
295 283 <div flex layout="row" layout-align="start center">
296 284 <md-button class="md-primary md-raised"
297 285 ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}">
298   - <md-tooltip md-direction="top">
299   - {{ 'extension.add-converter' | translate }}
300   - </md-tooltip>
301 286 <md-icon class="material-icons">add</md-icon>
302   - <span translate>action.add</span>
  287 + <span translate>extension.add-converter</span>
303 288 </md-button>
304 289 </div>
305 290 </v-pane-content>
... ... @@ -314,11 +299,8 @@
314 299 <div flex layout="row" layout-align="start center">
315 300 <md-button class="md-primary md-raised"
316 301 ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
317   - <md-tooltip md-direction="top">
318   - {{ 'extension.add-config' | translate }}
319   - </md-tooltip>
320 302 <md-icon class="material-icons">add</md-icon>
321   - <span translate>action.add</span>
  303 + <span translate>extension.add-config</span>
322 304 </md-button>
323 305 </div>
324 306 </v-pane-content>
... ...
  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 './extension-form.scss';
  18 +
  19 +/* eslint-disable angular/log */
  20 +
  21 +import extensionFormMqttTemplate from './extension-form-mqtt.tpl.html';
  22 +
  23 +/* eslint-enable import/no-unresolved, import/default */
  24 +
  25 +/*@ngInject*/
  26 +export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) {
  27 +
  28 + var linker = function(scope, element) {
  29 +
  30 + var template = $templateCache.get(extensionFormMqttTemplate);
  31 + element.html(template);
  32 +
  33 + scope.types = types;
  34 + scope.theForm = scope.$parent.theForm;
  35 +
  36 + scope.deviceNameExpressions = {
  37 + deviceNameJsonExpression: "extension.converter-json",
  38 + deviceNameTopicExpression: "extension.topic"
  39 + };
  40 + scope.deviceTypeExpressions = {
  41 + deviceTypeJsonExpression: "extension.converter-json",
  42 + deviceTypeTopicExpression: "extension.topic"
  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 + }
  52 +
  53 + scope.extensionCustomConverterOptions = {
  54 + useWrapMode: false,
  55 + mode: 'json',
  56 + showGutter: true,
  57 + showPrintMargin: true,
  58 + theme: 'github',
  59 + advanced: {
  60 + enableSnippets: true,
  61 + enableBasicAutocompletion: true,
  62 + enableLiveAutocompletion: true
  63 + },
  64 + onLoad: function(_ace) {
  65 + _ace.$blockScrolling = 1;
  66 + }
  67 + };
  68 +
  69 + scope.updateValidity = function () {
  70 + if(scope.brokers.length) {
  71 + for(let i=0;i<scope.brokers.length;i++) {
  72 + if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) {
  73 + if(!(scope.brokers[i].credentials.caCert && scope.brokers[i].credentials.privateKey && scope.brokers[i].credentials.cert)) {
  74 + scope.theForm.$setValidity('cert.PEM', false);
  75 + break;
  76 + } else {
  77 + scope.theForm.$setValidity('cert.PEM', true);
  78 + }
  79 + }
  80 + }
  81 + }
  82 + };
  83 +
  84 + scope.$watch('brokers', function() {
  85 + scope.updateValidity();
  86 + }, true);
  87 +
  88 + scope.addBroker = function() {
  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 + };
  102 + scope.brokers.push(newBroker);
  103 + };
  104 +
  105 + scope.removeBroker = function(broker) {
  106 + var index = scope.brokers.indexOf(broker);
  107 + if (index > -1) {
  108 + scope.brokers.splice(index, 1);
  109 + }
  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;
  118 + }
  119 +
  120 + scope.addMap = function(mapping) {
  121 + var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}};
  122 +
  123 + mapping.push(newMap);
  124 + };
  125 +
  126 + scope.removeMap = function(map, mapping) {
  127 + var index = mapping.indexOf(map);
  128 + if (index > -1) {
  129 + mapping.splice(index, 1);
  130 + }
  131 + };
  132 +
  133 + scope.addAttribute = function(attributes) {
  134 + var newAttribute = {type:"", key:"", value:""};
  135 + attributes.push(newAttribute);
  136 + };
  137 +
  138 + scope.removeAttribute = function(attribute, attributes) {
  139 + var index = attributes.indexOf(attribute);
  140 + if (index > -1) {
  141 + attributes.splice(index, 1);
  142 + }
  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 + };
  186 +
  187 + scope.changeCredentials = function(broker) {
  188 + var type = broker.credentials.type;
  189 + broker.credentials = {};
  190 + broker.credentials.type = type;
  191 + };
  192 +
  193 + scope.changeConverterType = function(map) {
  194 + if(map.converterType == "custom"){
  195 + map.converter = "";
  196 + }
  197 + if(map.converterType == "json") {
  198 + map.converter = {attributes:[],timeseries:[]};
  199 + }
  200 + };
  201 +
  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}";
  209 + }
  210 + }
  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)";
  223 + }
  224 + }
  225 + };
  226 +
  227 + scope.changeTypeExpression = function(converter) {
  228 + if(converter.typeExp == "deviceTypeJsonExpression") {
  229 + if(converter.deviceTypeTopicExpression) {
  230 + delete converter.deviceTypeTopicExpression;
  231 + }
  232 + }
  233 + if(converter.typeExp == "deviceTypeTopicExpression") {
  234 + if(converter.deviceTypeJsonExpression) {
  235 + delete converter.deviceTypeJsonExpression;
  236 + }
  237 + }
  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 + };
  269 +
  270 + scope.validateCustomConverter = function(model, editorName) {
  271 + if(model && model.length) {
  272 + try {
  273 + angular.fromJson(model);
  274 + scope.theForm[editorName].$setValidity('converterJSON', true);
  275 + } catch(e) {
  276 + scope.theForm[editorName].$setValidity('converterJSON', false);
  277 + }
  278 + }
  279 + };
  280 +
  281 + scope.fileAdded = function($file, broker, fileType) {
  282 + var reader = new FileReader();
  283 + reader.onload = function(event) {
  284 + scope.$apply(function() {
  285 + if(event.target.result) {
  286 + scope.theForm.$setDirty();
  287 + var addedFile = event.target.result;
  288 + if (addedFile && addedFile.length > 0) {
  289 + if(fileType == "caCert") {
  290 + broker.credentials.caCertFileName = $file.name;
  291 + broker.credentials.caCert = addedFile.replace(/^data.*base64,/, "");
  292 + }
  293 + if(fileType == "privateKey") {
  294 + broker.credentials.privateKeyFileName = $file.name;
  295 + broker.credentials.privateKey = addedFile.replace(/^data.*base64,/, "");
  296 + }
  297 + if(fileType == "Cert") {
  298 + broker.credentials.certFileName = $file.name;
  299 + broker.credentials.cert = addedFile.replace(/^data.*base64,/, "");
  300 + }
  301 + }
  302 + }
  303 + });
  304 + };
  305 + reader.readAsDataURL($file.file);
  306 + };
  307 +
  308 + scope.clearFile = function(broker, fileType) {
  309 + scope.theForm.$setDirty();
  310 + if(fileType == "caCert") {
  311 + broker.credentials.caCertFileName = null;
  312 + broker.credentials.caCert = null;
  313 + }
  314 + if(fileType == "privateKey") {
  315 + broker.credentials.privateKeyFileName = null;
  316 + broker.credentials.privateKey = null;
  317 + }
  318 + if(fileType == "Cert") {
  319 + broker.credentials.certFileName = null;
  320 + broker.credentials.cert = null;
  321 + }
  322 + };
  323 +
  324 + $compile(element.contents())(scope);
  325 + };
  326 +
  327 + return {
  328 + restrict: "A",
  329 + link: linker,
  330 + scope: {
  331 + config: "=",
  332 + isAdd: "="
  333 + }
  334 + }
  335 +}
\ No newline at end of file
... ...
... ... @@ -15,4 +15,846 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div>MQTT</div>
\ No newline at end of file
  18 +<md-card class="extension-form extension-mqtt">
  19 + <md-card-title name="testValid">
  20 + <md-card-title-text>
  21 + <span translate class="md-headline">extension.configuration</span>
  22 + </md-card-title-text>
  23 + </md-card-title>
  24 + <md-card-content>
  25 + <v-accordion id="mqtt-brokers-accordion" class="vAccordion--default">
  26 + <v-pane id="mqtt-brokers-pane" expanded="true">
  27 + <v-pane-header>
  28 + {{ 'extension.brokers' | translate }}
  29 + </v-pane-header>
  30 + <v-pane-content>
  31 + <div ng-if="brokers.length > 0">
  32 + <ol class="list-group">
  33 + <li class="list-group-item" ng-repeat="(brokerIndex,broker) in brokers">
  34 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeBroker(broker)" ng-hide="brokers.length < 2">
  35 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  36 + <md-tooltip md-direction="top">
  37 + {{ 'action.remove' | translate }}
  38 + </md-tooltip>
  39 + </md-button>
  40 + <md-card>
  41 + <md-card-content>
  42 + <section flex layout="row">
  43 + <md-input-container flex="40" class="md-block">
  44 + <label translate>extension.port</label>
  45 + <input required type="number" min="1" max="65535" name="mqttPort_{{brokerIndex}}" ng-model="broker.port">
  46 + <div ng-messages="theForm['mqttPort_' + brokerIndex].$error">
  47 + <div translate ng-message="required">extension.field-required</div>
  48 + <div translate ng-message="min">extension.port-range</div>
  49 + <div translate ng-message="max">extension.port-range</div>
  50 + </div>
  51 + </md-input-container>
  52 + <md-input-container flex="60" class="md-block">
  53 + <label translate>extension.host</label>
  54 + <input required name="mqttHost_{{brokerIndex}}" ng-model="broker.host">
  55 + <div ng-messages="theForm['mqttHost_' + brokerIndex].$error">
  56 + <div translate ng-message="required">extension.field-required</div>
  57 + </div>
  58 + </md-input-container>
  59 + </section>
  60 + <section flex layout="row">
  61 + <md-input-container flex="40" class="md-block">
  62 + <label translate>extension.retry-interval</label>
  63 + <input required type="number" name="mqttRetryInterval_{{brokerIndex}}" ng-model="broker.retryInterval">
  64 + <div ng-messages="theForm['mqttRetryInterval_' + brokerIndex].$error">
  65 + <div translate ng-message="required">extension.field-required</div>
  66 + </div>
  67 + </md-input-container>
  68 + <md-input-container flex="50" class="md-block">
  69 + <label translate>extension.credentials</label>
  70 + <md-select required name="mqttCredentials_{{brokerIndex}}" ng-model="broker.credentials.type" ng-change="changeCredentials(broker)">
  71 + <md-option ng-repeat="(credentialsType, credentialsValue) in types.mqttCredentialTypes" ng-value="credentialsValue.value">
  72 + {{credentialsValue.name | translate}}
  73 + </md-option>
  74 + </md-select>
  75 + </md-input-container>
  76 + <md-input-container flex="10" class="md-block">
  77 + <md-checkbox flex aria-label="{{ 'extension.ssl' | translate }}"
  78 + ng-model="broker.ssl">{{ 'extension.ssl' | translate }}
  79 + </md-checkbox>
  80 + </md-input-container>
  81 + </section>
  82 + <section flex layout="row" ng-if='broker.credentials.type == "basic"'>
  83 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttUsername_' + brokerIndex].$touched && theForm['mqttUsername_' + brokerIndex].$invalid">
  84 + <label translate>extension.username</label>
  85 + <input required name="mqttUsername_{{brokerIndex}}" ng-model="broker.credentials.username">
  86 + <div ng-messages="theForm['mqttUsername_' + brokerIndex].$error">
  87 + <div translate ng-message="required">extension.field-required</div>
  88 + </div>
  89 + </md-input-container>
  90 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttPassword_' + brokerIndex].$touched && theForm['mqttPassword_' + brokerIndex].$invalid">
  91 + <label translate>extension.password</label>
  92 + <input required name="mqttPassword_{{brokerIndex}}" ng-model="broker.credentials.password">
  93 + <div ng-messages="theForm['mqttPassword_' + brokerIndex].$error">
  94 + <div translate ng-message="required">extension.field-required</div>
  95 + </div>
  96 + </md-input-container>
  97 + </section>
  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'">
  100 + <label class="tb-label" translate>extension.ca-cert</label>
  101 + <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "caCert")' class="tb-file-select-container">
  102 + <div class="tb-file-clear-container">
  103 + <md-button ng-click='clearFile(broker, "caCert")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
  104 + <md-tooltip md-direction="top">
  105 + {{ 'action.remove' | translate }}
  106 + </md-tooltip>
  107 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
  108 + </md-button>
  109 + </div>
  110 + <div class="alert tb-flow-drop" flow-drop>
  111 + <label for="caCertSelect_{{brokerIndex}}" translate>extension.drop-file</label>
  112 + <input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="caCertSelect_{{brokerIndex}}">
  113 + </div>
  114 + </div>
  115 + </div>
  116 + <div class="dropdown-messages">
  117 + <div ng-if="!broker.credentials.caCertFileName" class="tb-error-message" translate>extension.no-file</div>
  118 + <div ng-if="broker.credentials.caCertFileName">{{broker.credentials.caCertFileName}}</div>
  119 + </div>
  120 + <div class="tb-container" ng-class="broker.credentials.privateKeyFileName ? 'ng-valid' : 'ng-invalid'">
  121 + <label class="tb-label" translate>extension.private-key</label>
  122 + <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "privateKey")' class="tb-file-select-container">
  123 + <div class="tb-file-clear-container">
  124 + <md-button ng-click='clearFile(broker, "privateKey")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
  125 + <md-tooltip md-direction="top">
  126 + {{ 'action.remove' | translate }}
  127 + </md-tooltip>
  128 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
  129 + </md-button>
  130 + </div>
  131 + <div class="alert tb-flow-drop" flow-drop>
  132 + <label for="privateKeySelect_{{brokerIndex}}" translate>extension.drop-file</label>
  133 + <input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="privateKeySelect_{{brokerIndex}}">
  134 + </div>
  135 + </div>
  136 + </div>
  137 + <div class="dropdown-messages">
  138 + <div ng-if="!broker.credentials.privateKeyFileName" class="tb-error-message" translate>extension.no-file</div>
  139 + <div ng-if="broker.credentials.privateKeyFileName">{{broker.credentials.privateKeyFileName}}</div>
  140 + </div>
  141 + <div class="tb-container" ng-class="broker.credentials.certFileName ? 'ng-valid' : 'ng-invalid'">
  142 + <label class="tb-label" translate>extension.cert</label>
  143 + <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "Cert")' class="tb-file-select-container">
  144 + <div class="tb-file-clear-container">
  145 + <md-button ng-click='clearFile(broker, "Cert")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
  146 + <md-tooltip md-direction="top">
  147 + {{ 'action.remove' | translate }}
  148 + </md-tooltip>
  149 + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
  150 + </md-button>
  151 + </div>
  152 + <div class="alert tb-flow-drop" flow-drop>
  153 + <label for="CertSelect_{{brokerIndex}}" translate>extension.drop-file</label>
  154 + <input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="CertSelect_{{brokerIndex}}">
  155 + </div>
  156 + </div>
  157 + </div>
  158 + <div class="dropdown-messages">
  159 + <div ng-if="!broker.credentials.certFileName" class="tb-error-message" translate>extension.no-file</div>
  160 + <div ng-if="broker.credentials.certFileName">{{broker.credentials.certFileName}}</div>
  161 + </div>
  162 + </section>
  163 +
  164 + <v-accordion id="mqtt-mapping-accordion" class="vAccordion--default">
  165 + <v-pane id="mqtt-mapping-pane">
  166 + <v-pane-header>
  167 + {{ 'extension.mapping' | translate }}
  168 + </v-pane-header>
  169 + <v-pane-content>
  170 + <div ng-if="broker.mapping.length > 0">
  171 + <ol class="list-group">
  172 + <li class="list-group-item" ng-repeat="(mapIndex,map) in broker.mapping">
  173 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeMap(map, broker.mapping)">
  174 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  175 + <md-tooltip md-direction="top">
  176 + {{ 'action.remove' | translate }}
  177 + </md-tooltip>
  178 + </md-button>
  179 + <md-card>
  180 + <md-card-content>
  181 + <section flex layout="row">
  182 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttConverterType_' + brokerIndex + mapIndex].$touched && theForm['mqttConverterType_' + brokerIndex + mapIndex].$invalid">
  183 + <label translate>extension.converter-type</label>
  184 + <md-select required name="mqttConverterType_{{brokerIndex}}{{mapIndex}}" ng-model="map.converterType" ng-change="changeConverterType(map)">
  185 + <md-option ng-repeat="(converterType, value) in types.mqttConverterTypes" ng-value="converterType">
  186 + {{value | translate}}
  187 + </md-option>
  188 + </md-select>
  189 + <div ng-messages="theForm['mqttConverterType_' + brokerIndex + mapIndex].$error">
  190 + <div translate ng-message="required">extension.field-required</div>
  191 + </div>
  192 + </md-input-container>
  193 + <md-input-container flex="60" class="md-block">
  194 + <label translate>extension.topic-filter</label>
  195 + <input required name="mqttTopicFilter_{{brokerIndex}}{{mapIndex}}" ng-model="map.topicFilter">
  196 + <div ng-messages="theForm['mqttTopicFilter_' + brokerIndex + mapIndex].$error">
  197 + <div translate ng-message="required">extension.field-required</div>
  198 + </div>
  199 + </md-input-container>
  200 + </section>
  201 +
  202 + <div ng-if='map.converterType =="json"' ng-init="map.converter.type = 'json'">
  203 + <section flex layout="row">
  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>
  206 + <md-select required name="mqttDeviceNameExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.nameExp" ng-change="changeNameExpression(map.converter)">
  207 + <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
  208 + {{value | translate}}
  209 + </md-option>
  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>
  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>
  217 + <input required name="mqttJsonNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameJsonExpression">
  218 + <div ng-messages="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$error">
  219 + <div translate ng-message="required">extension.field-required</div>
  220 + </div>
  221 + </md-input-container>
  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>
  224 + <input required name="mqttTopicNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameTopicExpression">
  225 + <div ng-messages="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$error">
  226 + <div translate ng-message="required">extension.field-required</div>
  227 + </div>
  228 + </md-input-container>
  229 + </section>
  230 + <section flex layout="row">
  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)">
  234 + <md-option ng-repeat="(key, value) in deviceTypeExpressions" ng-value='key'>
  235 + {{value | translate}}
  236 + </md-option>
  237 + </md-select>
  238 + <div ng-messages="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$error">
  239 + <div translate ng-message="required">extension.field-required</div>
  240 + </div>
  241 + </md-input-container>
  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">
  243 + <label translate>extension.json-type-expression</label>
  244 + <input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression">
  245 + <div ng-messages="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$error">
  246 + <div translate ng-message="required">extension.field-required</div>
  247 + </div>
  248 + </md-input-container>
  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">
  250 + <label translate>extension.topic-type-expression</label>
  251 + <input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression">
  252 + <div ng-messages="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$error">
  253 + <div translate ng-message="required">extension.field-required</div>
  254 + </div>
  255 + </md-input-container>
  256 + </section>
  257 + <section flex layout="row">
  258 + <md-input-container flex="40" class="md-block">
  259 + <label translate>extension.timeout</label>
  260 + <input type="number" name="mqttTimeout_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.timeout" parse-to-null>
  261 + </md-input-container>
  262 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$touched && theForm['mqttFilterExpression' + brokerIndex + mapIndex].$invalid">
  263 + <label translate>extension.filter-expression</label>
  264 + <input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
  265 + <div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error">
  266 + <div translate ng-message="required">extension.field-required</div>
  267 + </div>
  268 + </md-input-container>
  269 + </section>
  270 + </div>
  271 +
  272 + <div ng-if='map.converterType == "custom"'>
  273 + <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div>
  274 + <div flex class="tb-extension-custom-transformer-panel">
  275 + <div flex class="tb-extension-custom-transformer"
  276 + ui-ace="extensionCustomConverterOptions"
  277 + ng-model="map.converter"
  278 + name="mqttCustomConverter_{{brokerIndex}}{{mapIndex}}"
  279 + ng-change='validateCustomConverter(map.converter, "mqttCustomConverter_" + brokerIndex + mapIndex)'
  280 + required>
  281 + </div>
  282 + </div>
  283 + <div class="tb-error-messages" ng-messages="theForm['mqttCustomConverter_' + brokerIndex + mapIndex].$error" role="alert">
  284 + <div ng-message="required" class="tb-error-message" translate>extension.converter-json-required</div>
  285 + <div ng-message="converterJSON" class="tb-error-message" translate>extension.converter-json-parse</div>
  286 + </div>
  287 + </div>
  288 +
  289 + <v-accordion ng-if='map.converterType =="json"' id="mqtt-attributes-accordion" class="vAccordion--default">
  290 + <v-pane id="mqtt-attributes-pane">
  291 + <v-pane-header>
  292 + {{ 'extension.attributes' | translate }}
  293 + </v-pane-header>
  294 + <v-pane-content>
  295 + <div ng-if="map.converter.attributes.length > 0">
  296 + <ol class="list-group">
  297 + <li class="list-group-item" ng-repeat="(attributeIndex, attribute) in map.converter.attributes">
  298 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, map.converter.attributes)">
  299 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  300 + <md-tooltip md-direction="top">
  301 + {{ 'action.remove' | translate }}
  302 + </md-tooltip>
  303 + </md-button>
  304 + <md-card>
  305 + <md-card-content>
  306 + <section flex layout="row">
  307 + <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$invalid">
  308 + <label translate>extension.key</label>
  309 + <input required name="mqttAttributeKey_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.key">
  310 + <div ng-messages="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$error">
  311 + <div translate ng-message="required">extension.field-required</div>
  312 + </div>
  313 + </md-input-container>
  314 + <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$invalid">
  315 + <label translate>extension.type</label>
  316 + <md-select required name="mqttAttributeType_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.type">
  317 + <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
  318 + {{attrTypeValue | translate}}
  319 + </md-option>
  320 + </md-select>
  321 + <div ng-messages="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$error">
  322 + <div translate ng-message="required">extension.field-required</div>
  323 + </div>
  324 + </md-input-container>
  325 + </section>
  326 + <md-input-container class="md-block" md-is-error="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$invalid">
  327 + <label translate>extension.value</label>
  328 + <input required name="mqttAttributeValue_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.value">
  329 + <div ng-messages="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$error">
  330 + <div translate ng-message="required">extension.field-required</div>
  331 + </div>
  332 + </md-input-container>
  333 + </md-card-content>
  334 + </md-card>
  335 + </li>
  336 + </ol>
  337 + </div>
  338 + <div flex layout="row" layout-align="start center">
  339 + <md-button class="md-primary md-raised"
  340 + ng-click="addAttribute(map.converter.attributes)" aria-label="{{ 'action.add' | translate }}">
  341 + <md-icon class="material-icons">add</md-icon>
  342 + <span translate>extension.add-attribute</span>
  343 + </md-button>
  344 + </div>
  345 + </v-pane-content>
  346 + </v-pane>
  347 + </v-accordion>
  348 +
  349 + <v-accordion ng-if='map.converterType =="json"' id="mqtt-timeseries-accordion" class="vAccordion--default">
  350 + <v-pane id="mqtt-timeseries-pane">
  351 + <v-pane-header>
  352 + {{ 'extension.timeseries' | translate }}
  353 + </v-pane-header>
  354 + <v-pane-content>
  355 + <div ng-if="map.converter.timeseries.length > 0">
  356 + <ol class="list-group">
  357 + <li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in map.converter.timeseries">
  358 + <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, map.converter.timeseries)">
  359 + <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
  360 + <md-tooltip md-direction="top">
  361 + {{ 'action.remove' | translate }}
  362 + </md-tooltip>
  363 + </md-button>
  364 + <md-card>
  365 + <md-card-content>
  366 + <section flex layout="row">
  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>
  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.field-required</div>
  372 + </div>
  373 + </md-input-container>
  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>
  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">
  378 + {{attrTypeValue | translate}}
  379 + </md-option>
  380 + </md-select>
  381 + <div ng-messages="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$error">
  382 + <div translate ng-message="required">extension.field-required</div>
  383 + </div>
  384 + </md-input-container>
  385 + </section>
  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>
  388 + <input required name="mqttTimeseriesValue_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
  389 + <div ng-messages="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$error">
  390 + <div translate ng-message="required">extension.field-required</div>
  391 + </div>
  392 + </md-input-container>
  393 + </md-card-content>
  394 + </md-card>
  395 + </li>
  396 + </ol>
  397 + </div>
  398 + <div flex layout="row" layout-align="start center">
  399 + <md-button class="md-primary md-raised"
  400 + ng-click="addAttribute(map.converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
  401 + <md-icon class="material-icons">add</md-icon>
  402 + <span translate>extension.add-timeseries</span>
  403 + </md-button>
  404 + </div>
  405 + </v-pane-content>
  406 + </v-pane>
  407 + </v-accordion>
  408 +
  409 + </md-card-content>
  410 + </md-card>
  411 + </li>
  412 + </ol>
  413 + </div>
  414 + <div flex layout="row" layout-align="start center">
  415 + <md-button class="md-primary md-raised"
  416 + ng-click="addMap(broker.mapping)" aria-label="{{ 'action.add' | translate }}">
  417 + <md-icon class="material-icons">add</md-icon>
  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>
  553 + </md-button>
  554 + </div>
  555 + </v-pane-content>
  556 + </v-pane>
  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 +
  840 + </md-card-content>
  841 + </md-card>
  842 + </li>
  843 + </ol>
  844 + </div>
  845 +
  846 + <div flex layout="row" layout-align="start center">
  847 + <md-button class="md-primary md-raised"
  848 + ng-click="addBroker()" aria-label="{{ 'action.add' | translate }}">
  849 + <md-icon class="material-icons">add</md-icon>
  850 + <span translate>extension.add-broker</span>
  851 + </md-button>
  852 + </div>
  853 + </v-pane-content>
  854 + </v-pane>
  855 + </v-accordion>
  856 +<!--<pre>
  857 +{{config | json}}
  858 +</pre>-->
  859 + </md-card-content>
  860 +</md-card>
... ...
... ... @@ -56,16 +56,16 @@
56 56 <label translate>extension.opc-application-name</label>
57 57 <input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
58 58 <div ng-messages="theForm['applicationName_' + serverIndex].$error">
59   - <div translate ng-message="required">extension.opc-field-required</div>
  59 + <div translate ng-message="required">extension.field-required</div>
60 60 </div>
61 61 </md-input-container>
62 62
63 63
64   - <md-input-container flex="50" class="md-block">
  64 + <md-input-container flex="50" class="md-block" md-is-error="theForm['applicationUri_' + serverIndex].$touched && theForm['applicationUri_' + serverIndex].$invalid">
65 65 <label translate>extension.opc-application-uri</label>
66 66 <input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
67 67 <div ng-messages="theForm['applicationUri_' + serverIndex].$error">
68   - <div translate ng-message="required">extension.opc-field-required</div>
  68 + <div translate ng-message="required">extension.field-required</div>
69 69 </div>
70 70 </md-input-container>
71 71 </div>
... ... @@ -73,15 +73,15 @@
73 73
74 74 <div layout="row">
75 75 <md-input-container flex="50" class="md-block">
76   - <label translate>extension.opc-host</label>
  76 + <label translate>extension.host</label>
77 77 <input required name="host_{{serverIndex}}" ng-model="server.host">
78 78 <div ng-messages="theForm['host_' + serverIndex].$error">
79   - <div translate ng-message="required">extension.opc-field-required</div>
  79 + <div translate ng-message="required">extension.field-required</div>
80 80 </div>
81 81 </md-input-container>
82 82
83 83 <md-input-container flex="50" class="md-block">
84   - <label translate>extension.opc-port</label>
  84 + <label translate>extension.port</label>
85 85 <input type="number"
86 86 required
87 87 name="port_{{serverIndex}}"
... ... @@ -92,7 +92,7 @@
92 92 <div ng-messages="theForm['port_' + serverIndex].$error">
93 93 <div translate
94 94 ng-message="required"
95   - >extension.opc-field-required</div>
  95 + >extension.field-required</div>
96 96 <div translate
97 97 ng-message="min"
98 98 >Port should be in a range from 1 to 65535</div>
... ... @@ -113,12 +113,12 @@
113 113 <div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
114 114 <div translate
115 115 ng-message="required"
116   - >extension.opc-field-required</div>
  116 + >extension.field-required</div>
117 117 </div>
118 118 </md-input-container>
119 119
120 120 <md-input-container flex="50" class="md-block">
121   - <label translate>extension.opc-timeout-in-millis</label>
  121 + <label translate>extension.timeout</label>
122 122 <input type="number"
123 123 required name="timeoutInMillis_{{serverIndex}}"
124 124 ng-model="server.timeoutInMillis"
... ... @@ -126,7 +126,7 @@
126 126 <div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
127 127 <div translate
128 128 ng-message="required"
129   - >extension.opc-field-required</div>
  129 + >extension.field-required</div>
130 130 </div>
131 131 </md-input-container>
132 132 </div>
... ... @@ -145,7 +145,7 @@
145 145 <div ng-messages="theForm['securityType_' + serverIndex].$error">
146 146 <div translate
147 147 ng-message="required"
148   - >extension.opc-field-required</div>
  148 + >extension.field-required</div>
149 149 </div>
150 150 </md-input-container>
151 151
... ... @@ -157,24 +157,23 @@
157 157 >
158 158 <md-option ng-value="identityType"
159 159 ng-repeat="(identityType, identityValue) in types.extensionIdentityType"
160   - ><span ng-bind="::identityValue"></span></md-option>
  160 + ><span ng-bind="identityValue | translate"></span></md-option>
161 161 </md-select>
162 162 <div ng-messages="theForm['identityType_' + serverIndex].$error">
163 163 <div translate
164 164 ng-message="required"
165   - >extension.opc-field-required</div>
  165 + >extension.field-required</div>
166 166 </div>
167 167 </md-input-container>
168 168 </div>
169 169
170   -
171 170 <div ng-if="server.identity.type != 'username'">
172 171 <span class=""
173 172 ng-init="server.identity = {'type':'anonymous'}"></span>
174 173 </div>
175 174 <div layout="row" ng-if="server.identity.type == 'username'">
176   - <md-input-container flex="50" class="md-block">
177   - <label translate>extension.opc-username</label>
  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>
178 177 <input required
179 178 name="identityUsername_{{serverIndex}}"
180 179 ng-model="server.identity.username"
... ... @@ -182,25 +181,24 @@
182 181 <div ng-messages="theForm['identityUsername_' + serverIndex].$error">
183 182 <div translate
184 183 ng-message="required"
185   - >extension.opc-field-required</div>
  184 + >extension.field-required</div>
186 185 </div>
187 186 </md-input-container>
188 187
189   - <md-input-container flex="50" class="md-block">
190   - <label translate>extension.opc-password</label>
  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>
191 190 <input required
192 191 name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
193 192 <div ng-messages="theForm['identityPassword_' + serverIndex].$error">
194 193 <div translate
195 194 ng-message="required"
196   - >extension.opc-field-required</div>
  195 + >extension.field-required</div>
197 196 </div>
198 197 </md-input-container>
199 198 </div>
200 199
201   -
202 200 <v-accordion id="opc-attributes-accordion" class="vAccordion--default">
203   - <v-pane id="opc-attributes-pane">
  201 + <v-pane id="opc-attributes-pane" expanded="true">
204 202 <v-pane-header>
205 203 {{ 'extension.opc-keystore' | translate }}
206 204 </v-pane-header>
... ... @@ -212,38 +210,40 @@
212 210 <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
213 211 </md-select>
214 212 <div ng-messages="theForm['keystoreType_'+serverIndex].$error">
215   - <div translate ng-message="required">extension.opc-field-required</div>
  213 + <div translate ng-message="required">extension.field-required</div>
216 214 </div>
217 215 </md-input-container>
218 216
219   - <div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}">
220   - <span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
221   - <label class="tb-label" translate>extension.opc-keystore-location</label>
222   - <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
223   - <div class="tb-file-clear-container">
224   - <md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
225   - <md-tooltip md-direction="top">
226   - {{ 'action.remove' | translate }}
227   - </md-tooltip>
228   - <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
229   - </md-button>
230   - </div>
231   - <div class="alert tb-flow-drop" flow-drop>
232   - <label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
233   - <input flow-attrs="{accept:'.pfx,.p12'}"
234   - type="file"
235   - class="file-input"
236   - flow-btn id="dropFileKeystore_{{serverIndex}}"
237   - name="keystoreFile"
238   - ng-model="server.keystore.file"
239   - >
  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 240 </div>
241 241 </div>
242   - </div>
243   - <div>
244   - <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
245   - <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
246   - </div>
  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 247
248 248
249 249 <div flex layout="row">
... ... @@ -251,7 +251,7 @@
251 251 <label translate>extension.opc-keystore-password</label>
252 252 <input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
253 253 <div ng-messages="theForm['keystorePassword_' + serverIndex].$error">
254   - <div translate ng-message="required">extension.opc-field-required</div>
  254 + <div translate ng-message="required">extension.field-required</div>
255 255 </div>
256 256 </md-input-container>
257 257
... ... @@ -259,7 +259,7 @@
259 259 <label translate>extension.opc-keystore-alias</label>
260 260 <input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
261 261 <div ng-messages="theForm['keystoreAlias_' + serverIndex].$error">
262   - <div translate ng-message="required">extension.opc-field-required</div>
  262 + <div translate ng-message="required">extension.field-required</div>
263 263 </div>
264 264 </md-input-container>
265 265 </div>
... ... @@ -268,7 +268,7 @@
268 268 <label translate>extension.opc-keystore-key-password</label>
269 269 <input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
270 270 <div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error">
271   - <div translate ng-message="required">extension.opc-field-required</div>
  271 + <div translate ng-message="required">extension.field-required</div>
272 272 </div>
273 273 </md-input-container>
274 274
... ... @@ -282,7 +282,7 @@
282 282 >
283 283 <v-pane id="opc-attributes-pane">
284 284 <v-pane-header>
285   - {{ 'extension.opc-mapping' | translate }}
  285 + {{ 'extension.mapping' | translate }}
286 286 </v-pane-header>
287 287 <v-pane-content>
288 288 <div ng-if="server.mapping.length > 0">
... ... @@ -312,7 +312,7 @@
312 312 <div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
313 313 <div translate
314 314 ng-message="required"
315   - >extension.opc-field-required</div>
  315 + >extension.field-required</div>
316 316 </div>
317 317 </md-input-container>
318 318
... ... @@ -325,7 +325,7 @@
325 325 <div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
326 326 <div translate
327 327 ng-message="required"
328   - >extension.opc-field-required</div>
  328 + >extension.field-required</div>
329 329 </div>
330 330 </md-input-container>
331 331 </div>
... ... @@ -336,7 +336,7 @@
336 336 >
337 337 <v-pane id="opc-attributes-pane">
338 338 <v-pane-header>
339   - {{ 'extension.opc-mapping-attributes' | translate }}
  339 + {{ 'extension.attributes' | translate }}
340 340 </v-pane-header>
341 341 <v-pane-content>
342 342 <div ng-show="map.attributes.length > 0">
... ... @@ -369,7 +369,7 @@
369 369 <div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
370 370 <div translate
371 371 ng-message="required"
372   - >extension.opc-field-required</div>
  372 + >extension.field-required</div>
373 373 </div>
374 374 </md-input-container>
375 375 <md-input-container flex="40" class="md-block tb-container-for-select">
... ... @@ -400,7 +400,7 @@
400 400 <div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
401 401 <div translate
402 402 ng-message="required"
403   - >extension.opc-field-required</div>
  403 + >extension.field-required</div>
404 404 </div>
405 405 </md-input-container>
406 406
... ... @@ -417,11 +417,8 @@
417 417 ng-click="addNewAttribute(map.attributes)"
418 418 aria-label="{{ 'action.add' | translate }}"
419 419 >
420   - <md-tooltip md-direction="top">
421   - {{ 'extension.add-map' | translate }}
422   - </md-tooltip>
423 420 <md-icon class="material-icons">add</md-icon>
424   - <span translate>action.add</span>
  421 + <span translate>add-attribute</span>
425 422 </md-button>
426 423 </div>
427 424 </v-pane-content>
... ... @@ -431,7 +428,7 @@
431 428 <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
432 429 <v-pane id="opc-timeseries-pane">
433 430 <v-pane-header>
434   - {{ 'extension.opc-timeseries' | translate }}
  431 + {{ 'extension.timeseries' | translate }}
435 432 </v-pane-header>
436 433 <v-pane-content>
437 434 <div ng-show="map.timeseries.length > 0">
... ... @@ -460,7 +457,7 @@
460 457 <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
461 458 <div translate
462 459 ng-message="required"
463   - >extension.opc-field-required</div>
  460 + >extension.field-required</div>
464 461 </div>
465 462 </md-input-container>
466 463 <md-input-container flex="40"
... ... @@ -480,7 +477,7 @@
480 477 <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
481 478 <div translate
482 479 ng-message="required"
483   - >extension.opc-field-required</div>
  480 + >extension.field-required</div>
484 481 </div>
485 482 </md-input-container>
486 483 </section>
... ... @@ -503,11 +500,8 @@
503 500 ng-click="addNewAttribute(map.timeseries)"
504 501 aria-label="{{ 'action.add' | translate }}"
505 502 >
506   - <md-tooltip md-direction="top">
507   - {{ 'extension.add-timeseries' | translate }}
508   - </md-tooltip>
509 503 <md-icon class="material-icons">add</md-icon>
510   - <span translate>action.add</span>
  504 + <span translate>extension.add-timeseries</span>
511 505 </md-button>
512 506 </div>
513 507 </v-pane-content>
... ... @@ -528,11 +522,8 @@
528 522 ng-click="addMap(server.mapping)"
529 523 aria-label="{{ 'action.add' | translate }}"
530 524 >
531   - <md-tooltip md-direction="top">
532   - {{ 'extension.add-map' | translate }}
533   - </md-tooltip>
534 525 <md-icon class="material-icons">add</md-icon>
535   - <span translate>action.add</span>
  526 + <span translate>extension.add-map</span>
536 527 </md-button>
537 528 </div>
538 529 </v-pane-content>
... ... @@ -553,7 +544,7 @@
553 544 aria-label="{{ 'action.add' | translate }}"
554 545 >
555 546 <md-icon class="material-icons">add</md-icon>
556   - <span translate>extension.opc-add-another-server</span>
  547 + <span translate>extension.opc-add-server</span>
557 548 </md-button>
558 549 </div>
559 550
... ...
... ... @@ -22,6 +22,23 @@
22 22 margin-top: 0;
23 23 padding-left: 3px;
24 24 }
  25 + .tb-container {
  26 + width:100%;
  27 + }
  28 + .dropdown-messages {
  29 + .tb-error-message {
  30 + padding: 5px 0 0 0;
  31 + }
  32 + }
  33 + .dropdown-section {
  34 + margin-bottom: 30px;
  35 + }
  36 +}
  37 +
  38 +.extension-form.extension-mqtt {
  39 + md-checkbox{
  40 + margin-left: 10px;
  41 + }
25 42 }
26 43
27 44 .tb-extension-custom-transformer-panel {
... ...
... ... @@ -16,12 +16,14 @@
16 16
17 17 import ExtensionTableDirective from './extension-table.directive';
18 18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
  19 +import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
19 20 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
20 21 import {ParseToNull} from './extension-dialog.controller';
21 22
22 23 export default angular.module('thingsboard.extension', [])
23 24 .directive('tbExtensionTable', ExtensionTableDirective)
24 25 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
  26 + .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
25 27 .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
26 28 .directive('parseToNull', ParseToNull)
27 29 .name;
\ No newline at end of file
... ...
... ... @@ -738,13 +738,8 @@ export default angular.module('thingsboard.locale', [])
738 738 "id": "Id",
739 739 "extension-id": "Extension id",
740 740 "extension-type": "Extension type",
741   - "transformer-json": "JSON*",
742   - "id-required": "Extension id is required.",
  741 + "transformer-json": "JSON *",
743 742 "unique-id-required": "Current extension id already exists.",
744   - "type-required": "Extension type is required.",
745   - "required-type": "Type is required.",
746   - "required-key": "Key is required.",
747   - "required-value": "Value is required.",
748 743 "delete": "Delete extension",
749 744 "add": "Add extension",
750 745 "edit": "Edit extension",
... ... @@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', [])
754 749 "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
755 750 "converters": "Converters",
756 751 "converter-id": "Converter id",
757   - "converter-id-required": "Converter id is required.",
758 752 "configuration": "Configuration",
759 753 "converter-configurations": "Converter configurations",
760 754 "token": "Security token",
761 755 "add-converter": "Add converter",
762   - "add-converter-prompt": "Please add converter",
763 756 "add-config": "Add converter configuration",
764   - "add-config-prompt": "Please add converter configuration",
765 757 "device-name-expression": "Device name expression",
766   - "device-name-expression-required": "Device name expression is required.",
767 758 "device-type-expression": "Device type expression",
768   - "device-type-expression-required": "Device type expression is required.",
769 759 "custom": "Custom",
770 760 "to-double": "To Double",
771 761 "transformer": "Transformer",
... ... @@ -777,7 +767,6 @@ export default angular.module('thingsboard.locale', [])
777 767 "timeseries": "Timeseries",
778 768 "add-timeseries": "Add timeseries",
779 769
780   -
781 770 "sync": {
782 771 "status": "Status",
783 772 "sync": "Sync",
... ... @@ -785,18 +774,69 @@ export default angular.module('thingsboard.locale', [])
785 774 "last-sync-time": "Last sync time",
786 775 },
787 776
788   - "opc-field-required": "Field is required",
  777 +
  778 + "field-required": "Field is required",
  779 + "brokers": "Brokers",
  780 + "add-broker": "Add broker",
  781 + "host": "Host",
  782 + "port": "Port",
  783 + "port-range": "Port should be in a range from 1 to 65535.",
  784 + "ssl": "Ssl",
  785 + "credentials": "Credentials",
  786 + "username": "Username",
  787 + "password": "Password",
  788 + "retry-interval": "Retry interval in milliseconds",
  789 + "anonymous": "Anonymous",
  790 + "basic": "Basic",
  791 + "pem": "PEM",
  792 + "ca-cert": "CA certificate file *",
  793 + "private-key": "Private key file *",
  794 + "cert": "Certificate file *",
  795 + "no-file": "No file selected.",
  796 + "drop-file": "Drop a file or click to select a file to upload.",
  797 + "mapping": "Mapping",
  798 + "topic-filter": "Topic filter",
  799 + "converter-type": "Converter type",
  800 + "converter-json": "Json",
  801 + "json-name-expression": "Device name json expression",
  802 + "topic-name-expression": "Device name topic expression",
  803 + "json-type-expression": "Device type json expression",
  804 + "topic-type-expression": "Device type topic expression",
  805 + "attribute-key-expression": "Attribute key expression",
  806 + "attr-json-key-expression": "Attribute key json expression",
  807 + "attr-topic-key-expression": "Attribute key topic expression",
  808 + "request-id-expression": "Request id expression",
  809 + "request-id-json-expression": "Request id json expression",
  810 + "request-id-topic-expression": "Request id topic expression",
  811 + "response-topic-expression": "Response topic expression",
  812 + "value-expression": "Value expression",
  813 + "topic": "Topic",
  814 + "timeout": "Timeout in milliseconds",
  815 + "converter-json-required": "Converter json is required.",
  816 + "converter-json-parse": "Unable to parse converter json.",
  817 + "filter-expression": "Filter expression",
  818 + "connect-requests": "Connect requests",
  819 + "add-connect-request": "Add connect request",
  820 + "disconnect-requests": "Disconnect requests",
  821 + "add-disconnect-request": "Add disconnect request",
  822 + "attribute-requests": "Attribute requests",
  823 + "add-attribute-request": "Add attribute request",
  824 + "attribute-updates": "Attribute updates",
  825 + "add-attribute-update": "Add attribute update",
  826 + "server-side-rpc": "Server side RPC",
  827 + "add-server-side-rpc-request": "Add server-side RPC request",
  828 + "device-name-filter": "Device name filter",
  829 + "attribute-filter": "Attribute filter",
  830 + "method-filter": "Method filter",
  831 + "request-topic-expression": "Request topic expression",
  832 + "response-timeout": "Response timeout in milliseconds",
  833 + "topic-expression": "Topic expression",
  834 + "client-scope": "Client scope",
789 835 "opc-server": "Servers",
790   - "opc-add-server-hint": "Add server",
791   - "opc-add-server-prompt": "Please add server",
792   - "opc-server-id": "Server id",
793   - "opc-timeseries": "Timeseries",
  836 + "opc-add-server": "Add server",
794 837 "opc-application-name": "Application name",
795 838 "opc-application-uri": "Application uri",
796   - "opc-host": "Host",
797   - "opc-port": "Port",
798 839 "opc-scan-period-in-seconds": "Scan period in seconds",
799   - "opc-timeout-in-millis": "Timeout in milliseconds",
800 840 "opc-security": "Security",
801 841 "opc-identity": "Identity",
802 842 "opc-keystore": "Keystore",
... ... @@ -806,19 +846,14 @@ export default angular.module('thingsboard.locale', [])
806 846 "opc-keystore-password":"Password",
807 847 "opc-keystore-alias":"Alias",
808 848 "opc-keystore-key-password":"Key password",
809   - "opc-mapping":"Mapping",
810 849 "opc-device-node-pattern":"Device node pattern",
811 850 "opc-device-name-pattern":"Device name pattern",
812   - "opc-mapping-attributes":"Mapping attributes",
813   - "opc-username":"Username",
814   - "opc-password":"Password",
815   - "opc-add-another-server":"Add another server",
816 851 },
817 852 "fullscreen": {
818 853 "expand": "Expand to fullscreen",
819 854 "exit": "Exit fullscreen",
820 855 "toggle": "Toggle fullscreen mode",
821   - "fullscreen": "Fullscreen",
  856 + "fullscreen": "Fullscreen"
822 857 },
823 858 "function": {
824 859 "function": "Function"
... ... @@ -936,7 +971,6 @@ export default angular.module('thingsboard.locale', [])
936 971 "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
937 972 "copyId": "Copy plugin Id",
938 973 "idCopiedMessage": "Plugin Id has been copied to clipboard"
939   -
940 974 },
941 975 "position": {
942 976 "top": "Top",
... ...