Commit 6c5b33705aa69bdee8688cc79234f698f86408f6

Authored by oleg
2 parents 0098a94d 33451e05

final

@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 "angular-material": "1.1.1", 30 "angular-material": "1.1.1",
31 "angular-material-data-table": "^0.10.9", 31 "angular-material-data-table": "^0.10.9",
32 "angular-material-icons": "^0.7.1", 32 "angular-material-icons": "^0.7.1",
  33 + "angular-material-expansion-panel": "^0.7.2",
33 "angular-messages": "1.5.8", 34 "angular-messages": "1.5.8",
34 "angular-route": "1.5.8", 35 "angular-route": "1.5.8",
35 "angular-sanitize": "1.5.8", 36 "angular-sanitize": "1.5.8",
@@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router'; @@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router';
39 import angularJwt from 'angular-jwt'; 39 import angularJwt from 'angular-jwt';
40 import 'angular-drag-and-drop-lists'; 40 import 'angular-drag-and-drop-lists';
41 import mdDataTable from 'angular-material-data-table'; 41 import mdDataTable from 'angular-material-data-table';
  42 +import 'angular-material-expansion-panel';
42 import ngTouch from 'angular-touch'; 43 import ngTouch from 'angular-touch';
43 import 'angular-carousel'; 44 import 'angular-carousel';
44 import 'clipboard'; 45 import 'clipboard';
@@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css'; @@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css';
82 import 'mdPickers/dist/mdPickers.min.css'; 83 import 'mdPickers/dist/mdPickers.min.css';
83 import 'angular-hotkeys/build/hotkeys.min.css'; 84 import 'angular-hotkeys/build/hotkeys.min.css';
84 import 'angular-carousel/dist/angular-carousel.min.css'; 85 import 'angular-carousel/dist/angular-carousel.min.css';
  86 +import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
85 import '../scss/main.scss'; 87 import '../scss/main.scss';
86 88
87 import AppConfig from './app.config'; 89 import AppConfig from './app.config';
@@ -103,6 +105,7 @@ angular.module('thingsboard', [ @@ -103,6 +105,7 @@ angular.module('thingsboard', [
103 angularJwt, 105 angularJwt,
104 'dndLists', 106 'dndLists',
105 mdDataTable, 107 mdDataTable,
  108 + 'material.components.expansionPanels',
106 ngTouch, 109 ngTouch,
107 'angular-carousel', 110 'angular-carousel',
108 'ngclipboard', 111 'ngclipboard',
@@ -332,6 +332,24 @@ export default angular.module('thingsboard.types', []) @@ -332,6 +332,24 @@ export default angular.module('thingsboard.types', [])
332 toDouble: 'extension.to-double', 332 toDouble: 'extension.to-double',
333 custom: 'extension.custom' 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 extensionOpcSecurityTypes: { 353 extensionOpcSecurityTypes: {
336 Basic128Rsa15: "Basic128Rsa15", 354 Basic128Rsa15: "Basic128Rsa15",
337 Basic256: "Basic256", 355 Basic256: "Basic256",
@@ -339,8 +357,8 @@ export default angular.module('thingsboard.types', []) @@ -339,8 +357,8 @@ export default angular.module('thingsboard.types', [])
339 None: "None" 357 None: "None"
340 }, 358 },
341 extensionIdentityType: { 359 extensionIdentityType: {
342 - anonymous: "anonymous",  
343 - username: "username" 360 + anonymous: "extension.anonymous",
  361 + username: "extension.username"
344 }, 362 },
345 extensionKeystoreType: { 363 extensionKeystoreType: {
346 PKCS12: "PKCS12", 364 PKCS12: "PKCS12",
@@ -30,11 +30,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -30,11 +30,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
30 vm.allExtensions = allExtensions; 30 vm.allExtensions = allExtensions;
31 31
32 32
33 - if (extension) { // Editing  
34 - //vm.configuration = vm.extension.configuration; 33 + if (extension) {
35 vm.extension = angular.copy(extension); 34 vm.extension = angular.copy(extension);
36 editTransformers(vm.extension); 35 editTransformers(vm.extension);
37 - } else { // Add new 36 + } else {
38 vm.extension = {}; 37 vm.extension = {};
39 } 38 }
40 39
@@ -65,8 +64,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -65,8 +64,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
65 64
66 vm.save = save; 65 vm.save = save;
67 function save() { 66 function save() {
68 - saveTransformers();  
69 -  
70 let $errorElement = angular.element('[name=theForm]').find('.ng-invalid'); 67 let $errorElement = angular.element('[name=theForm]').find('.ng-invalid');
71 68
72 if ($errorElement.length) { 69 if ($errorElement.length) {
@@ -78,11 +75,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -78,11 +75,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
78 75
79 if ($errorElementTop !== $mdDialogTop) { 76 if ($errorElementTop !== $mdDialogTop) {
80 angular.element('md-dialog-content').animate({ 77 angular.element('md-dialog-content').animate({
81 - scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 20 78 + scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 50
82 }, 500); 79 }, 500);
83 $errorElement.eq(0).focus(); 80 $errorElement.eq(0).focus();
84 } 81 }
85 -  
86 } else { 82 } else {
87 83
88 if(vm.isAdd) { 84 if(vm.isAdd) {
@@ -94,6 +90,9 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -94,6 +90,9 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
94 } 90 }
95 } 91 }
96 92
  93 + $mdDialog.hide();
  94 + saveTransformers();
  95 +
97 var editedValue = angular.toJson(vm.allExtensions); 96 var editedValue = angular.toJson(vm.allExtensions);
98 97
99 attributeService 98 attributeService
@@ -104,8 +103,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate, @@ -104,8 +103,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
104 [{key:"configuration", value:editedValue}] 103 [{key:"configuration", value:editedValue}]
105 ) 104 )
106 .then(function success() { 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,21 +128,60 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
131 }; 128 };
132 129
133 function saveTransformers() { 130 function saveTransformers() {
134 - var config = vm.extension.configuration.converterConfigurations;  
135 if(vm.extension.type == types.extensionType.http) { 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,8 +189,8 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
153 } 189 }
154 190
155 function editTransformers(extension) { 191 function editTransformers(extension) {
156 - var config = extension.configuration.converterConfigurations;  
157 if(extension.type == types.extensionType.http) { 192 if(extension.type == types.extensionType.http) {
  193 + var config = extension.configuration.converterConfigurations;
158 for(let i=0;i<config.length;i++) { 194 for(let i=0;i<config.length;i++) {
159 for(let j=0;j<config[i].converters.length;j++){ 195 for(let j=0;j<config[i].converters.length;j++){
160 for(let k=0;k<config[i].converters[j].attributes.length;k++){ 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,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,16 +36,16 @@
36 <md-content class="md-padding" layout="column"> 36 <md-content class="md-padding" layout="column">
37 <fieldset ng-disabled="loading"> 37 <fieldset ng-disabled="loading">
38 <section flex layout="row"> 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 <label translate>extension.extension-id</label> 40 <label translate>extension.extension-id</label>
41 <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()"> 41 <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
42 <div ng-messages="theForm.extensionId.$error"> 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 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div> 44 <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
45 </div> 45 </div>
46 </md-input-container> 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 <label translate>extension.extension-type</label> 49 <label translate>extension.extension-type</label>
50 50
51 <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type"> 51 <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
@@ -55,16 +55,14 @@ @@ -55,16 +55,14 @@
55 </md-select> 55 </md-select>
56 56
57 <div ng-messages="theForm.extensionType.$error"> 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 </div> 59 </div>
60 </md-input-container> 60 </md-input-container>
61 </section> 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 </fieldset> 65 </fieldset>
67 - <!--<div>{{vm.extension}}</div>-->  
68 </md-content> 66 </md-content>
69 </div> 67 </div>
70 </md-dialog-content> 68 </md-dialog-content>
@@ -80,5 +78,4 @@ @@ -80,5 +78,4 @@
80 </md-button> 78 </md-button>
81 </md-dialog-actions> 79 </md-dialog-actions>
82 </form> 80 </form>
83 -</md-dialog>  
84 - 81 +</md-dialog>
@@ -43,7 +43,7 @@ export default function ExtensionTableDirective() { @@ -43,7 +43,7 @@ export default function ExtensionTableDirective() {
43 } 43 }
44 44
45 /*@ngInject*/ 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 let vm = this; 48 let vm = this;
49 49
@@ -83,51 +83,6 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, @@ -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 function enterFilterMode() { 86 function enterFilterMode() {
132 vm.query.search = ''; 87 vm.query.search = '';
133 } 88 }
@@ -288,57 +243,73 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, @@ -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 }
@@ -67,7 +67,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -67,7 +67,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
67 if (index > -1) { 67 if (index > -1) {
68 scope.converterConfigs.splice(index, 1); 68 scope.converterConfigs.splice(index, 1);
69 } 69 }
70 - scope.theForm.$setDirty();  
71 }; 70 };
72 71
73 scope.addConverter = function(converters) { 72 scope.addConverter = function(converters) {
@@ -85,7 +84,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -85,7 +84,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
85 if (index > -1) { 84 if (index > -1) {
86 converters.splice(index, 1); 85 converters.splice(index, 1);
87 } 86 }
88 - scope.theForm.$setDirty();  
89 }; 87 };
90 88
91 scope.addAttribute = function(attributes) { 89 scope.addAttribute = function(attributes) {
@@ -98,13 +96,9 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -98,13 +96,9 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
98 if (index > -1) { 96 if (index > -1) {
99 attributes.splice(index, 1); 97 attributes.splice(index, 1);
100 } 98 }
101 - scope.theForm.$setDirty();  
102 }; 99 };
103 100
104 101
105 -  
106 -  
107 -  
108 if(scope.isAdd) { 102 if(scope.isAdd) {
109 scope.converterConfigs = scope.config.converterConfigurations; 103 scope.converterConfigs = scope.config.converterConfigurations;
110 scope.addConverterConfig(); 104 scope.addConverterConfig();
@@ -112,28 +106,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr @@ -112,28 +106,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
112 scope.converterConfigs = scope.config.converterConfigurations; 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 scope.transformerTypeChange = function(attribute) { 109 scope.transformerTypeChange = function(attribute) {
138 attribute.transformer = ""; 110 attribute.transformer = "";
139 }; 111 };
@@ -23,14 +23,11 @@ @@ -23,14 +23,11 @@
23 </md-card-title> 23 </md-card-title>
24 <md-card-content> 24 <md-card-content>
25 <v-accordion id="http-converter-configs-accordion" class="vAccordion--default"> 25 <v-accordion id="http-converter-configs-accordion" class="vAccordion--default">
26 - <v-pane id="http-converters-pane" expanded="isAdd"> 26 + <v-pane id="http-converters-pane" expanded="true">
27 <v-pane-header> 27 <v-pane-header>
28 {{ 'extension.converter-configurations' | translate }} 28 {{ 'extension.converter-configurations' | translate }}
29 </v-pane-header> 29 </v-pane-header>
30 <v-pane-content> 30 <v-pane-content>
31 - <div ng-if="converterConfigs.length === 0">  
32 - <span translate layout-align="center center" class="tb-prompt">extension.add-config-prompt</span>  
33 - </div>  
34 <div ng-if="converterConfigs.length > 0"> 31 <div ng-if="converterConfigs.length > 0">
35 <ol class="list-group"> 32 <ol class="list-group">
36 <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs"> 33 <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs">
@@ -47,11 +44,11 @@ @@ -47,11 +44,11 @@
47 <md-card> 44 <md-card>
48 <md-card-content> 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 <label translate>extension.converter-id</label> 48 <label translate>extension.converter-id</label>
52 <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId"> 49 <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
53 <div ng-messages="theForm['httpConverterId_' + configIndex].$error"> 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 </div> 52 </div>
56 </md-input-container> 53 </md-input-container>
57 <md-input-container class="md-block"> 54 <md-input-container class="md-block">
@@ -59,14 +56,11 @@ @@ -59,14 +56,11 @@
59 <input name="httpToken" ng-model="config.token" parse-to-null> 56 <input name="httpToken" ng-model="config.token" parse-to-null>
60 </md-input-container> 57 </md-input-container>
61 <v-accordion id="http-converters-accordion" class="vAccordion--default"> 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 <v-pane-header> 60 <v-pane-header>
64 {{ 'extension.converters' | translate }} 61 {{ 'extension.converters' | translate }}
65 </v-pane-header> 62 </v-pane-header>
66 <v-pane-content> 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 <div ng-if="config.converters.length > 0"> 64 <div ng-if="config.converters.length > 0">
71 <ol class="list-group"> 65 <ol class="list-group">
72 <li class="list-group-item" 66 <li class="list-group-item"
@@ -84,18 +78,18 @@ @@ -84,18 +78,18 @@
84 </md-button> 78 </md-button>
85 <md-card> 79 <md-card>
86 <md-card-content> 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 <label translate>extension.device-name-expression</label> 82 <label translate>extension.device-name-expression</label>
89 <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression"> 83 <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
90 <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error"> 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 </div> 86 </div>
93 </md-input-container> 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 <label translate>extension.device-type-expression</label> 89 <label translate>extension.device-type-expression</label>
96 <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression"> 90 <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
97 <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error"> 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 </div> 93 </div>
100 </md-input-container> 94 </md-input-container>
101 95
@@ -117,14 +111,14 @@ @@ -117,14 +111,14 @@
117 <md-card> 111 <md-card>
118 <md-card-content> 112 <md-card-content>
119 <section flex layout="row"> 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 <label translate>extension.key</label> 115 <label translate>extension.key</label>
122 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key"> 116 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
123 <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error"> 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 </div> 119 </div>
126 </md-input-container> 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 <label translate>extension.type</label> 122 <label translate>extension.type</label>
129 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type"> 123 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
130 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> 124 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -132,16 +126,16 @@ @@ -132,16 +126,16 @@
132 </md-option> 126 </md-option>
133 </md-select> 127 </md-select>
134 <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error"> 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 </div> 130 </div>
137 </md-input-container> 131 </md-input-container>
138 </section> 132 </section>
139 <section flex layout="row"> 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 <label translate>extension.value</label> 135 <label translate>extension.value</label>
142 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value"> 136 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
143 <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error"> 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 </div> 139 </div>
146 </md-input-container> 140 </md-input-container>
147 141
@@ -182,11 +176,8 @@ @@ -182,11 +176,8 @@
182 <div flex layout="row" layout-align="start center"> 176 <div flex layout="row" layout-align="start center">
183 <md-button class="md-primary md-raised" 177 <md-button class="md-primary md-raised"
184 ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 179 <md-icon class="material-icons">add</md-icon>
189 - <span translate>action.add</span> 180 + <span translate>extension.add-attribute</span>
190 </md-button> 181 </md-button>
191 </div> 182 </div>
192 </v-pane-content> 183 </v-pane-content>
@@ -212,14 +203,14 @@ @@ -212,14 +203,14 @@
212 <md-card> 203 <md-card>
213 <md-card-content> 204 <md-card-content>
214 <section flex layout="row"> 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 <label translate>extension.key</label> 207 <label translate>extension.key</label>
217 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key"> 208 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
218 <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error"> 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 </div> 211 </div>
221 </md-input-container> 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 <label translate>extension.type</label> 214 <label translate>extension.type</label>
224 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type"> 215 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
225 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType"> 216 <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -227,16 +218,16 @@ @@ -227,16 +218,16 @@
227 </md-option> 218 </md-option>
228 </md-select> 219 </md-select>
229 <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error"> 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 </div> 222 </div>
232 </md-input-container> 223 </md-input-container>
233 </section> 224 </section>
234 <section flex layout="row"> 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 <label translate>extension.value</label> 227 <label translate>extension.value</label>
237 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value"> 228 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
238 <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error"> 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 </div> 231 </div>
241 </md-input-container> 232 </md-input-container>
242 233
@@ -277,11 +268,8 @@ @@ -277,11 +268,8 @@
277 <div flex layout="row" layout-align="start center"> 268 <div flex layout="row" layout-align="start center">
278 <md-button class="md-primary md-raised" 269 <md-button class="md-primary md-raised"
279 ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 271 <md-icon class="material-icons">add</md-icon>
284 - <span translate>action.add</span> 272 + <span translate>extension.add-timeseries</span>
285 </md-button> 273 </md-button>
286 </div> 274 </div>
287 </v-pane-content> 275 </v-pane-content>
@@ -295,11 +283,8 @@ @@ -295,11 +283,8 @@
295 <div flex layout="row" layout-align="start center"> 283 <div flex layout="row" layout-align="start center">
296 <md-button class="md-primary md-raised" 284 <md-button class="md-primary md-raised"
297 ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}"> 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 <md-icon class="material-icons">add</md-icon> 286 <md-icon class="material-icons">add</md-icon>
302 - <span translate>action.add</span> 287 + <span translate>extension.add-converter</span>
303 </md-button> 288 </md-button>
304 </div> 289 </div>
305 </v-pane-content> 290 </v-pane-content>
@@ -314,11 +299,8 @@ @@ -314,11 +299,8 @@
314 <div flex layout="row" layout-align="start center"> 299 <div flex layout="row" layout-align="start center">
315 <md-button class="md-primary md-raised" 300 <md-button class="md-primary md-raised"
316 ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}"> 301 ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
317 - <md-tooltip md-direction="top">  
318 - {{ 'extension.add-config' | translate }}  
319 - </md-tooltip>  
320 <md-icon class="material-icons">add</md-icon> 302 <md-icon class="material-icons">add</md-icon>
321 - <span translate>action.add</span> 303 + <span translate>extension.add-config</span>
322 </md-button> 304 </md-button>
323 </div> 305 </div>
324 </v-pane-content> 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 +}
@@ -15,4 +15,846 @@ @@ -15,4 +15,846 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<div>MQTT</div>  
  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,16 +56,16 @@
56 <label translate>extension.opc-application-name</label> 56 <label translate>extension.opc-application-name</label>
57 <input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName"> 57 <input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
58 <div ng-messages="theForm['applicationName_' + serverIndex].$error"> 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 </div> 60 </div>
61 </md-input-container> 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 <label translate>extension.opc-application-uri</label> 65 <label translate>extension.opc-application-uri</label>
66 <input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri"> 66 <input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
67 <div ng-messages="theForm['applicationUri_' + serverIndex].$error"> 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 </div> 69 </div>
70 </md-input-container> 70 </md-input-container>
71 </div> 71 </div>
@@ -73,15 +73,15 @@ @@ -73,15 +73,15 @@
73 73
74 <div layout="row"> 74 <div layout="row">
75 <md-input-container flex="50" class="md-block"> 75 <md-input-container flex="50" class="md-block">
76 - <label translate>extension.opc-host</label> 76 + <label translate>extension.host</label>
77 <input required name="host_{{serverIndex}}" ng-model="server.host"> 77 <input required name="host_{{serverIndex}}" ng-model="server.host">
78 <div ng-messages="theForm['host_' + serverIndex].$error"> 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 </div> 80 </div>
81 </md-input-container> 81 </md-input-container>
82 82
83 <md-input-container flex="50" class="md-block"> 83 <md-input-container flex="50" class="md-block">
84 - <label translate>extension.opc-port</label> 84 + <label translate>extension.port</label>
85 <input type="number" 85 <input type="number"
86 required 86 required
87 name="port_{{serverIndex}}" 87 name="port_{{serverIndex}}"
@@ -92,7 +92,7 @@ @@ -92,7 +92,7 @@
92 <div ng-messages="theForm['port_' + serverIndex].$error"> 92 <div ng-messages="theForm['port_' + serverIndex].$error">
93 <div translate 93 <div translate
94 ng-message="required" 94 ng-message="required"
95 - >extension.opc-field-required</div> 95 + >extension.field-required</div>
96 <div translate 96 <div translate
97 ng-message="min" 97 ng-message="min"
98 >Port should be in a range from 1 to 65535</div> 98 >Port should be in a range from 1 to 65535</div>
@@ -113,12 +113,12 @@ @@ -113,12 +113,12 @@
113 <div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error"> 113 <div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
114 <div translate 114 <div translate
115 ng-message="required" 115 ng-message="required"
116 - >extension.opc-field-required</div> 116 + >extension.field-required</div>
117 </div> 117 </div>
118 </md-input-container> 118 </md-input-container>
119 119
120 <md-input-container flex="50" class="md-block"> 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 <input type="number" 122 <input type="number"
123 required name="timeoutInMillis_{{serverIndex}}" 123 required name="timeoutInMillis_{{serverIndex}}"
124 ng-model="server.timeoutInMillis" 124 ng-model="server.timeoutInMillis"
@@ -126,7 +126,7 @@ @@ -126,7 +126,7 @@
126 <div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error"> 126 <div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
127 <div translate 127 <div translate
128 ng-message="required" 128 ng-message="required"
129 - >extension.opc-field-required</div> 129 + >extension.field-required</div>
130 </div> 130 </div>
131 </md-input-container> 131 </md-input-container>
132 </div> 132 </div>
@@ -145,7 +145,7 @@ @@ -145,7 +145,7 @@
145 <div ng-messages="theForm['securityType_' + serverIndex].$error"> 145 <div ng-messages="theForm['securityType_' + serverIndex].$error">
146 <div translate 146 <div translate
147 ng-message="required" 147 ng-message="required"
148 - >extension.opc-field-required</div> 148 + >extension.field-required</div>
149 </div> 149 </div>
150 </md-input-container> 150 </md-input-container>
151 151
@@ -157,24 +157,23 @@ @@ -157,24 +157,23 @@
157 > 157 >
158 <md-option ng-value="identityType" 158 <md-option ng-value="identityType"
159 ng-repeat="(identityType, identityValue) in types.extensionIdentityType" 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 </md-select> 161 </md-select>
162 <div ng-messages="theForm['identityType_' + serverIndex].$error"> 162 <div ng-messages="theForm['identityType_' + serverIndex].$error">
163 <div translate 163 <div translate
164 ng-message="required" 164 ng-message="required"
165 - >extension.opc-field-required</div> 165 + >extension.field-required</div>
166 </div> 166 </div>
167 </md-input-container> 167 </md-input-container>
168 </div> 168 </div>
169 169
170 -  
171 <div ng-if="server.identity.type != 'username'"> 170 <div ng-if="server.identity.type != 'username'">
172 <span class="" 171 <span class=""
173 ng-init="server.identity = {'type':'anonymous'}"></span> 172 ng-init="server.identity = {'type':'anonymous'}"></span>
174 </div> 173 </div>
175 <div layout="row" ng-if="server.identity.type == 'username'"> 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 <input required 177 <input required
179 name="identityUsername_{{serverIndex}}" 178 name="identityUsername_{{serverIndex}}"
180 ng-model="server.identity.username" 179 ng-model="server.identity.username"
@@ -182,25 +181,24 @@ @@ -182,25 +181,24 @@
182 <div ng-messages="theForm['identityUsername_' + serverIndex].$error"> 181 <div ng-messages="theForm['identityUsername_' + serverIndex].$error">
183 <div translate 182 <div translate
184 ng-message="required" 183 ng-message="required"
185 - >extension.opc-field-required</div> 184 + >extension.field-required</div>
186 </div> 185 </div>
187 </md-input-container> 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 <input required 190 <input required
192 name="identityPassword_{{serverIndex}}" ng-model="server.identity.password"> 191 name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
193 <div ng-messages="theForm['identityPassword_' + serverIndex].$error"> 192 <div ng-messages="theForm['identityPassword_' + serverIndex].$error">
194 <div translate 193 <div translate
195 ng-message="required" 194 ng-message="required"
196 - >extension.opc-field-required</div> 195 + >extension.field-required</div>
197 </div> 196 </div>
198 </md-input-container> 197 </md-input-container>
199 </div> 198 </div>
200 199
201 -  
202 <v-accordion id="opc-attributes-accordion" class="vAccordion--default"> 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 <v-pane-header> 202 <v-pane-header>
205 {{ 'extension.opc-keystore' | translate }} 203 {{ 'extension.opc-keystore' | translate }}
206 </v-pane-header> 204 </v-pane-header>
@@ -212,38 +210,40 @@ @@ -212,38 +210,40 @@
212 <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option> 210 <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
213 </md-select> 211 </md-select>
214 <div ng-messages="theForm['keystoreType_'+serverIndex].$error"> 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 </div> 214 </div>
217 </md-input-container> 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 </div> 240 </div>
241 </div> 241 </div>
242 - </div>  
243 - <div>  
244 - <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>  
245 - <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>  
246 - </div> 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 <div flex layout="row"> 249 <div flex layout="row">
@@ -251,7 +251,7 @@ @@ -251,7 +251,7 @@
251 <label translate>extension.opc-keystore-password</label> 251 <label translate>extension.opc-keystore-password</label>
252 <input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password"> 252 <input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
253 <div ng-messages="theForm['keystorePassword_' + serverIndex].$error"> 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 </div> 255 </div>
256 </md-input-container> 256 </md-input-container>
257 257
@@ -259,7 +259,7 @@ @@ -259,7 +259,7 @@
259 <label translate>extension.opc-keystore-alias</label> 259 <label translate>extension.opc-keystore-alias</label>
260 <input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias"> 260 <input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
261 <div ng-messages="theForm['keystoreAlias_' + serverIndex].$error"> 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 </div> 263 </div>
264 </md-input-container> 264 </md-input-container>
265 </div> 265 </div>
@@ -268,7 +268,7 @@ @@ -268,7 +268,7 @@
268 <label translate>extension.opc-keystore-key-password</label> 268 <label translate>extension.opc-keystore-key-password</label>
269 <input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword"> 269 <input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
270 <div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error"> 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 </div> 272 </div>
273 </md-input-container> 273 </md-input-container>
274 274
@@ -282,7 +282,7 @@ @@ -282,7 +282,7 @@
282 > 282 >
283 <v-pane id="opc-attributes-pane"> 283 <v-pane id="opc-attributes-pane">
284 <v-pane-header> 284 <v-pane-header>
285 - {{ 'extension.opc-mapping' | translate }} 285 + {{ 'extension.mapping' | translate }}
286 </v-pane-header> 286 </v-pane-header>
287 <v-pane-content> 287 <v-pane-content>
288 <div ng-if="server.mapping.length > 0"> 288 <div ng-if="server.mapping.length > 0">
@@ -312,7 +312,7 @@ @@ -312,7 +312,7 @@
312 <div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error"> 312 <div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
313 <div translate 313 <div translate
314 ng-message="required" 314 ng-message="required"
315 - >extension.opc-field-required</div> 315 + >extension.field-required</div>
316 </div> 316 </div>
317 </md-input-container> 317 </md-input-container>
318 318
@@ -325,7 +325,7 @@ @@ -325,7 +325,7 @@
325 <div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error"> 325 <div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
326 <div translate 326 <div translate
327 ng-message="required" 327 ng-message="required"
328 - >extension.opc-field-required</div> 328 + >extension.field-required</div>
329 </div> 329 </div>
330 </md-input-container> 330 </md-input-container>
331 </div> 331 </div>
@@ -336,7 +336,7 @@ @@ -336,7 +336,7 @@
336 > 336 >
337 <v-pane id="opc-attributes-pane"> 337 <v-pane id="opc-attributes-pane">
338 <v-pane-header> 338 <v-pane-header>
339 - {{ 'extension.opc-mapping-attributes' | translate }} 339 + {{ 'extension.attributes' | translate }}
340 </v-pane-header> 340 </v-pane-header>
341 <v-pane-content> 341 <v-pane-content>
342 <div ng-show="map.attributes.length > 0"> 342 <div ng-show="map.attributes.length > 0">
@@ -369,7 +369,7 @@ @@ -369,7 +369,7 @@
369 <div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error"> 369 <div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
370 <div translate 370 <div translate
371 ng-message="required" 371 ng-message="required"
372 - >extension.opc-field-required</div> 372 + >extension.field-required</div>
373 </div> 373 </div>
374 </md-input-container> 374 </md-input-container>
375 <md-input-container flex="40" class="md-block tb-container-for-select"> 375 <md-input-container flex="40" class="md-block tb-container-for-select">
@@ -400,7 +400,7 @@ @@ -400,7 +400,7 @@
400 <div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error"> 400 <div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
401 <div translate 401 <div translate
402 ng-message="required" 402 ng-message="required"
403 - >extension.opc-field-required</div> 403 + >extension.field-required</div>
404 </div> 404 </div>
405 </md-input-container> 405 </md-input-container>
406 406
@@ -417,11 +417,8 @@ @@ -417,11 +417,8 @@
417 ng-click="addNewAttribute(map.attributes)" 417 ng-click="addNewAttribute(map.attributes)"
418 aria-label="{{ 'action.add' | translate }}" 418 aria-label="{{ 'action.add' | translate }}"
419 > 419 >
420 - <md-tooltip md-direction="top">  
421 - {{ 'extension.add-map' | translate }}  
422 - </md-tooltip>  
423 <md-icon class="material-icons">add</md-icon> 420 <md-icon class="material-icons">add</md-icon>
424 - <span translate>action.add</span> 421 + <span translate>add-attribute</span>
425 </md-button> 422 </md-button>
426 </div> 423 </div>
427 </v-pane-content> 424 </v-pane-content>
@@ -431,7 +428,7 @@ @@ -431,7 +428,7 @@
431 <v-accordion id="opc-timeseries-accordion" class="vAccordion--default"> 428 <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
432 <v-pane id="opc-timeseries-pane"> 429 <v-pane id="opc-timeseries-pane">
433 <v-pane-header> 430 <v-pane-header>
434 - {{ 'extension.opc-timeseries' | translate }} 431 + {{ 'extension.timeseries' | translate }}
435 </v-pane-header> 432 </v-pane-header>
436 <v-pane-content> 433 <v-pane-content>
437 <div ng-show="map.timeseries.length > 0"> 434 <div ng-show="map.timeseries.length > 0">
@@ -460,7 +457,7 @@ @@ -460,7 +457,7 @@
460 <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error"> 457 <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
461 <div translate 458 <div translate
462 ng-message="required" 459 ng-message="required"
463 - >extension.opc-field-required</div> 460 + >extension.field-required</div>
464 </div> 461 </div>
465 </md-input-container> 462 </md-input-container>
466 <md-input-container flex="40" 463 <md-input-container flex="40"
@@ -480,7 +477,7 @@ @@ -480,7 +477,7 @@
480 <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error"> 477 <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
481 <div translate 478 <div translate
482 ng-message="required" 479 ng-message="required"
483 - >extension.opc-field-required</div> 480 + >extension.field-required</div>
484 </div> 481 </div>
485 </md-input-container> 482 </md-input-container>
486 </section> 483 </section>
@@ -503,11 +500,8 @@ @@ -503,11 +500,8 @@
503 ng-click="addNewAttribute(map.timeseries)" 500 ng-click="addNewAttribute(map.timeseries)"
504 aria-label="{{ 'action.add' | translate }}" 501 aria-label="{{ 'action.add' | translate }}"
505 > 502 >
506 - <md-tooltip md-direction="top">  
507 - {{ 'extension.add-timeseries' | translate }}  
508 - </md-tooltip>  
509 <md-icon class="material-icons">add</md-icon> 503 <md-icon class="material-icons">add</md-icon>
510 - <span translate>action.add</span> 504 + <span translate>extension.add-timeseries</span>
511 </md-button> 505 </md-button>
512 </div> 506 </div>
513 </v-pane-content> 507 </v-pane-content>
@@ -528,11 +522,8 @@ @@ -528,11 +522,8 @@
528 ng-click="addMap(server.mapping)" 522 ng-click="addMap(server.mapping)"
529 aria-label="{{ 'action.add' | translate }}" 523 aria-label="{{ 'action.add' | translate }}"
530 > 524 >
531 - <md-tooltip md-direction="top">  
532 - {{ 'extension.add-map' | translate }}  
533 - </md-tooltip>  
534 <md-icon class="material-icons">add</md-icon> 525 <md-icon class="material-icons">add</md-icon>
535 - <span translate>action.add</span> 526 + <span translate>extension.add-map</span>
536 </md-button> 527 </md-button>
537 </div> 528 </div>
538 </v-pane-content> 529 </v-pane-content>
@@ -553,7 +544,7 @@ @@ -553,7 +544,7 @@
553 aria-label="{{ 'action.add' | translate }}" 544 aria-label="{{ 'action.add' | translate }}"
554 > 545 >
555 <md-icon class="material-icons">add</md-icon> 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 </md-button> 548 </md-button>
558 </div> 549 </div>
559 550
@@ -22,6 +22,23 @@ @@ -22,6 +22,23 @@
22 margin-top: 0; 22 margin-top: 0;
23 padding-left: 3px; 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 .tb-extension-custom-transformer-panel { 44 .tb-extension-custom-transformer-panel {
@@ -16,12 +16,14 @@ @@ -16,12 +16,14 @@
16 16
17 import ExtensionTableDirective from './extension-table.directive'; 17 import ExtensionTableDirective from './extension-table.directive';
18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive'; 18 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
  19 +import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
19 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive'; 20 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
20 import {ParseToNull} from './extension-dialog.controller'; 21 import {ParseToNull} from './extension-dialog.controller';
21 22
22 export default angular.module('thingsboard.extension', []) 23 export default angular.module('thingsboard.extension', [])
23 .directive('tbExtensionTable', ExtensionTableDirective) 24 .directive('tbExtensionTable', ExtensionTableDirective)
24 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective) 25 .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
  26 + .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
25 .directive('tbExtensionFormOpc', ExtensionFormOpcDirective) 27 .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
26 .directive('parseToNull', ParseToNull) 28 .directive('parseToNull', ParseToNull)
27 .name; 29 .name;
@@ -738,13 +738,8 @@ export default angular.module('thingsboard.locale', []) @@ -738,13 +738,8 @@ export default angular.module('thingsboard.locale', [])
738 "id": "Id", 738 "id": "Id",
739 "extension-id": "Extension id", 739 "extension-id": "Extension id",
740 "extension-type": "Extension type", 740 "extension-type": "Extension type",
741 - "transformer-json": "JSON*",  
742 - "id-required": "Extension id is required.", 741 + "transformer-json": "JSON *",
743 "unique-id-required": "Current extension id already exists.", 742 "unique-id-required": "Current extension id already exists.",
744 - "type-required": "Extension type is required.",  
745 - "required-type": "Type is required.",  
746 - "required-key": "Key is required.",  
747 - "required-value": "Value is required.",  
748 "delete": "Delete extension", 743 "delete": "Delete extension",
749 "add": "Add extension", 744 "add": "Add extension",
750 "edit": "Edit extension", 745 "edit": "Edit extension",
@@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', []) @@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', [])
754 "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", 749 "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
755 "converters": "Converters", 750 "converters": "Converters",
756 "converter-id": "Converter id", 751 "converter-id": "Converter id",
757 - "converter-id-required": "Converter id is required.",  
758 "configuration": "Configuration", 752 "configuration": "Configuration",
759 "converter-configurations": "Converter configurations", 753 "converter-configurations": "Converter configurations",
760 "token": "Security token", 754 "token": "Security token",
761 "add-converter": "Add converter", 755 "add-converter": "Add converter",
762 - "add-converter-prompt": "Please add converter",  
763 "add-config": "Add converter configuration", 756 "add-config": "Add converter configuration",
764 - "add-config-prompt": "Please add converter configuration",  
765 "device-name-expression": "Device name expression", 757 "device-name-expression": "Device name expression",
766 - "device-name-expression-required": "Device name expression is required.",  
767 "device-type-expression": "Device type expression", 758 "device-type-expression": "Device type expression",
768 - "device-type-expression-required": "Device type expression is required.",  
769 "custom": "Custom", 759 "custom": "Custom",
770 "to-double": "To Double", 760 "to-double": "To Double",
771 "transformer": "Transformer", 761 "transformer": "Transformer",
@@ -777,7 +767,6 @@ export default angular.module('thingsboard.locale', []) @@ -777,7 +767,6 @@ export default angular.module('thingsboard.locale', [])
777 "timeseries": "Timeseries", 767 "timeseries": "Timeseries",
778 "add-timeseries": "Add timeseries", 768 "add-timeseries": "Add timeseries",
779 769
780 -  
781 "sync": { 770 "sync": {
782 "status": "Status", 771 "status": "Status",
783 "sync": "Sync", 772 "sync": "Sync",
@@ -785,18 +774,69 @@ export default angular.module('thingsboard.locale', []) @@ -785,18 +774,69 @@ export default angular.module('thingsboard.locale', [])
785 "last-sync-time": "Last sync time", 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 "opc-server": "Servers", 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 "opc-application-name": "Application name", 837 "opc-application-name": "Application name",
795 "opc-application-uri": "Application uri", 838 "opc-application-uri": "Application uri",
796 - "opc-host": "Host",  
797 - "opc-port": "Port",  
798 "opc-scan-period-in-seconds": "Scan period in seconds", 839 "opc-scan-period-in-seconds": "Scan period in seconds",
799 - "opc-timeout-in-millis": "Timeout in milliseconds",  
800 "opc-security": "Security", 840 "opc-security": "Security",
801 "opc-identity": "Identity", 841 "opc-identity": "Identity",
802 "opc-keystore": "Keystore", 842 "opc-keystore": "Keystore",
@@ -806,19 +846,14 @@ export default angular.module('thingsboard.locale', []) @@ -806,19 +846,14 @@ export default angular.module('thingsboard.locale', [])
806 "opc-keystore-password":"Password", 846 "opc-keystore-password":"Password",
807 "opc-keystore-alias":"Alias", 847 "opc-keystore-alias":"Alias",
808 "opc-keystore-key-password":"Key password", 848 "opc-keystore-key-password":"Key password",
809 - "opc-mapping":"Mapping",  
810 "opc-device-node-pattern":"Device node pattern", 849 "opc-device-node-pattern":"Device node pattern",
811 "opc-device-name-pattern":"Device name pattern", 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 "fullscreen": { 852 "fullscreen": {
818 "expand": "Expand to fullscreen", 853 "expand": "Expand to fullscreen",
819 "exit": "Exit fullscreen", 854 "exit": "Exit fullscreen",
820 "toggle": "Toggle fullscreen mode", 855 "toggle": "Toggle fullscreen mode",
821 - "fullscreen": "Fullscreen", 856 + "fullscreen": "Fullscreen"
822 }, 857 },
823 "function": { 858 "function": {
824 "function": "Function" 859 "function": "Function"
@@ -936,7 +971,6 @@ export default angular.module('thingsboard.locale', []) @@ -936,7 +971,6 @@ export default angular.module('thingsboard.locale', [])
936 "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.", 971 "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
937 "copyId": "Copy plugin Id", 972 "copyId": "Copy plugin Id",
938 "idCopiedMessage": "Plugin Id has been copied to clipboard" 973 "idCopiedMessage": "Plugin Id has been copied to clipboard"
939 -  
940 }, 974 },
941 "position": { 975 "position": {
942 "top": "Top", 976 "top": "Top",