thingsboard-developers

final

11/29/2017 3:10:39 PM

Details

ui/package.json 1(+1 -0)

diff --git a/ui/package.json b/ui/package.json
index cb8fdbd..81f4574 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -30,6 +30,7 @@
     "angular-material": "1.1.1",
     "angular-material-data-table": "^0.10.9",
     "angular-material-icons": "^0.7.1",
+    "angular-material-expansion-panel": "^0.7.2",
     "angular-messages": "1.5.8",
     "angular-route": "1.5.8",
     "angular-sanitize": "1.5.8",
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index 4a6f3f0..5b19089 100644
--- a/ui/src/app/app.js
+++ b/ui/src/app/app.js
@@ -39,6 +39,7 @@ import uiRouter from 'angular-ui-router';
 import angularJwt from 'angular-jwt';
 import 'angular-drag-and-drop-lists';
 import mdDataTable from 'angular-material-data-table';
+import 'angular-material-expansion-panel';
 import ngTouch from 'angular-touch';
 import 'angular-carousel';
 import 'clipboard';
@@ -82,6 +83,7 @@ import 'md-color-picker/dist/mdColorPicker.min.css';
 import 'mdPickers/dist/mdPickers.min.css';
 import 'angular-hotkeys/build/hotkeys.min.css';
 import 'angular-carousel/dist/angular-carousel.min.css';
+import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
 import '../scss/main.scss';
 
 import AppConfig from './app.config';
@@ -103,6 +105,7 @@ angular.module('thingsboard', [
     angularJwt,
     'dndLists',
     mdDataTable,
+    'material.components.expansionPanels',
     ngTouch,
     'angular-carousel',
     'ngclipboard',
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 52c08d2..5b3c9e4 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -332,6 +332,24 @@ export default angular.module('thingsboard.types', [])
                 toDouble: 'extension.to-double',
                 custom: 'extension.custom'
             },
+            mqttConverterTypes: {
+                json: 'extension.converter-json',
+                custom: 'extension.custom'
+            },
+            mqttCredentialTypes: {
+                anonymous:  {
+                    value: "anonymous",
+                    name: "extension.anonymous"
+                },
+                basic: {
+                    value: "basic",
+                    name: "extension.basic"
+                },
+                pem: {
+                    value: "cert.PEM",
+                    name: "extension.pem"
+                }
+            },
             extensionOpcSecurityTypes: {
                 Basic128Rsa15: "Basic128Rsa15",
                 Basic256: "Basic256",
@@ -339,8 +357,8 @@ export default angular.module('thingsboard.types', [])
                 None: "None"
             },
             extensionIdentityType: {
-                anonymous: "anonymous",
-                username: "username"
+                anonymous: "extension.anonymous",
+                username: "extension.username"
             },
             extensionKeystoreType: {
                 PKCS12: "PKCS12",
diff --git a/ui/src/app/extension/extension-dialog.controller.js b/ui/src/app/extension/extension-dialog.controller.js
index cfec9cf..fe5825c 100644
--- a/ui/src/app/extension/extension-dialog.controller.js
+++ b/ui/src/app/extension/extension-dialog.controller.js
@@ -30,11 +30,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
     vm.allExtensions = allExtensions;
 
 
-    if (extension) { // Editing
-        //vm.configuration = vm.extension.configuration;
+    if (extension) {
         vm.extension = angular.copy(extension);
         editTransformers(vm.extension);
-    } else { // Add new
+    } else {
         vm.extension = {};
     }
 
@@ -65,8 +64,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
 
     vm.save = save;
     function save() {
-        saveTransformers();
-
         let $errorElement = angular.element('[name=theForm]').find('.ng-invalid');
 
         if ($errorElement.length) {
@@ -78,11 +75,10 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
 
             if ($errorElementTop !== $mdDialogTop) {
                 angular.element('md-dialog-content').animate({
-                    scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 20
+                    scrollTop: $mdDialogScroll + ($errorElementTop - $mdDialogTop) - 50
                 }, 500);
                 $errorElement.eq(0).focus();
             }
-
         } else {
 
             if(vm.isAdd) {
@@ -94,6 +90,9 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
                 }
             }
 
+            $mdDialog.hide();
+            saveTransformers();
+
             var editedValue = angular.toJson(vm.allExtensions);
 
             attributeService
@@ -104,8 +103,6 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
                     [{key:"configuration", value:editedValue}]
                 )
                 .then(function success() {
-                    $scope.theForm.$setPristine();
-                    $mdDialog.hide();
                 });
 
         }
@@ -131,21 +128,60 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
     };
 
     function saveTransformers() {
-        var config = vm.extension.configuration.converterConfigurations;
         if(vm.extension.type == types.extensionType.http) {
-            for(let i=0;i<config.length;i++) {
-                for(let j=0;j<config[i].converters.length;j++){
-                    for(let k=0;k<config[i].converters[j].attributes.length;k++){
-                        if(config[i].converters[j].attributes[k].transformerType == "toDouble"){
-                            config[i].converters[j].attributes[k].transformer = {type: "intToDouble"};
+            var config = vm.extension.configuration.converterConfigurations;
+            if(config && config.length > 0) {
+                for(let i=0;i<config.length;i++) {
+                    for(let j=0;j<config[i].converters.length;j++){
+                        for(let k=0;k<config[i].converters[j].attributes.length;k++){
+                            if(config[i].converters[j].attributes[k].transformerType == "toDouble"){
+                                config[i].converters[j].attributes[k].transformer = {type: "intToDouble"};
+                            }
+                            delete config[i].converters[j].attributes[k].transformerType;
+                        }
+                        for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
+                            if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){
+                                config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"};
+                            }
+                            delete config[i].converters[j].timeseries[l].transformerType;
                         }
-                        delete config[i].converters[j].attributes[k].transformerType;
                     }
-                    for(let l=0;l<config[i].converters[j].timeseries.length;l++) {
-                        if(config[i].converters[j].timeseries[l].transformerType == "toDouble"){
-                            config[i].converters[j].timeseries[l].transformer = {type: "intToDouble"};
+                }
+            }
+        }
+        if(vm.extension.type == types.extensionType.mqtt) {
+            var brokers = vm.extension.configuration.brokers;
+            if(brokers && brokers.length > 0) {
+                for(let i=0;i<brokers.length;i++) {
+                    if(brokers[i].mapping && brokers[i].mapping.length > 0) {
+                        for(let j=0;j<brokers[i].mapping.length;j++) {
+                            if(brokers[i].mapping[j].converterType == "json") {
+                                delete brokers[i].mapping[j].converter.nameExp;
+                                delete brokers[i].mapping[j].converter.typeExp;
+                            }
+                            delete brokers[i].mapping[j].converterType;
+                        }
+                    }
+                    if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) {
+                        for(let j=0;j<brokers[i].connectRequests.length;j++) {
+                            delete brokers[i].connectRequests[j].nameExp;
+                        }
+                    }
+                    if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) {
+                        for(let j=0;j<brokers[i].disconnectRequests.length;j++) {
+                            delete brokers[i].disconnectRequests[j].nameExp;
+                        }
+                    }
+                    if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) {
+                        for(let j=0;j<brokers[i].attributeRequests.length;j++) {
+                            delete brokers[i].attributeRequests[j].nameExp;
+                        }
+                        for(let j=0;j<brokers[i].attributeRequests.length;j++) {
+                            delete brokers[i].attributeRequests[j].attrKey;
+                        }
+                        for(let j=0;j<brokers[i].attributeRequests.length;j++) {
+                            delete brokers[i].attributeRequests[j].requestId;
                         }
-                        delete config[i].converters[j].timeseries[l].transformerType;
                     }
                 }
             }
@@ -153,8 +189,8 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
     }
 
     function editTransformers(extension) {
-        var config = extension.configuration.converterConfigurations;
         if(extension.type == types.extensionType.http) {
+            var config = extension.configuration.converterConfigurations;
             for(let i=0;i<config.length;i++) {
                 for(let j=0;j<config[i].converters.length;j++){
                     for(let k=0;k<config[i].converters[j].attributes.length;k++){
@@ -180,6 +216,67 @@ export default function ExtensionDialogController($scope, $mdDialog, $translate,
                 }
             }
         }
+        if(extension.type == types.extensionType.mqtt) {
+            var brokers = extension.configuration.brokers;
+            for(let i=0;i<brokers.length;i++) {
+                if(brokers[i].mapping && brokers[i].mapping.length > 0) {
+                    for(let j=0;j<brokers[i].mapping.length;j++) {
+                        if(brokers[i].mapping[j].converter.type == "json") {
+                            if(brokers[i].mapping[j].converter.deviceNameTopicExpression) {
+                                brokers[i].mapping[j].converter.nameExp = "deviceNameTopicExpression";
+                            } else {
+                                brokers[i].mapping[j].converter.nameExp = "deviceNameJsonExpression";
+                            }
+                            if(brokers[i].mapping[j].converter.deviceTypeTopicExpression) {
+                                brokers[i].mapping[j].converter.typeExp = "deviceTypeTopicExpression";
+                            } else {
+                                brokers[i].mapping[j].converter.typeExp = "deviceTypeJsonExpression";
+                            }
+                            brokers[i].mapping[j].converterType = "json";
+                        } else {
+                            brokers[i].mapping[j].converterType = "custom";
+                        }
+                    }
+                }
+                if(brokers[i].connectRequests && brokers[i].connectRequests.length > 0) {
+                    for(let j=0;j<brokers[i].connectRequests.length;j++) {
+                        if(brokers[i].connectRequests[j].deviceNameTopicExpression) {
+                            brokers[i].connectRequests[j].nameExp = "deviceNameTopicExpression";
+                        } else {
+                            brokers[i].connectRequests[j].nameExp = "deviceNameJsonExpression";
+                        }
+                    }
+                }
+                if(brokers[i].disconnectRequests && brokers[i].disconnectRequests.length > 0) {
+                    for(let j=0;j<brokers[i].disconnectRequests.length;j++) {
+                        if(brokers[i].disconnectRequests[j].deviceNameTopicExpression) {
+                            brokers[i].disconnectRequests[j].nameExp = "deviceNameTopicExpression";
+                        } else {
+                            brokers[i].disconnectRequests[j].nameExp = "deviceNameJsonExpression";
+                        }
+                    }
+                }
+                if(brokers[i].attributeRequests && brokers[i].attributeRequests.length > 0) {
+                    for(let j=0;j<brokers[i].attributeRequests.length;j++) {
+                        if(brokers[i].attributeRequests[j].deviceNameTopicExpression) {
+                            brokers[i].attributeRequests[j].nameExp = "deviceNameTopicExpression";
+                        } else {
+                            brokers[i].attributeRequests[j].nameExp = "deviceNameJsonExpression";
+                        }
+                        if(brokers[i].attributeRequests[j].attributeKeyTopicExpression) {
+                            brokers[i].attributeRequests[j].attrKey = "attributeKeyTopicExpression";
+                        } else {
+                            brokers[i].attributeRequests[j].attrKey = "attributeKeyJsonExpression";
+                        }
+                        if(brokers[i].attributeRequests[j].requestIdTopicExpression) {
+                            brokers[i].attributeRequests[j].requestId = "requestIdTopicExpression";
+                        } else {
+                            brokers[i].attributeRequests[j].requestId = "requestIdJsonExpression";
+                        }
+                    }
+                }
+            }
+        }
     }
 }
 
diff --git a/ui/src/app/extension/extension-dialog.tpl.html b/ui/src/app/extension/extension-dialog.tpl.html
index 2336a60..73c33d8 100644
--- a/ui/src/app/extension/extension-dialog.tpl.html
+++ b/ui/src/app/extension/extension-dialog.tpl.html
@@ -36,16 +36,16 @@
                 <md-content class="md-padding" layout="column">
                     <fieldset ng-disabled="loading">
                         <section flex layout="row">
-                            <md-input-container flex="60" class="md-block">
+                            <md-input-container flex="60" class="md-block" md-is-error="theForm.extensionId.$touched && theForm.extensionId.$invalid">
                                 <label translate>extension.extension-id</label>
                                 <input required name="extensionId" ng-model="vm.extension.id" ng-change="vm.validateId()">
                                 <div ng-messages="theForm.extensionId.$error">
-                                    <div translate ng-message="required">extension.id-required</div>
+                                    <div translate ng-message="required">extension.field-required</div>
                                     <div translate ng-message="uniqueIdValidation">extension.unique-id-required</div>
                                 </div>
                             </md-input-container>
 
-                            <md-input-container flex="40" class="md-block">
+                            <md-input-container flex="40" class="md-block" md-is-error="theForm.extensionType.$touched && theForm.extensionType.$invalid">
                                 <label translate>extension.extension-type</label>
 
                                 <md-select ng-disabled="!vm.isAdd" required name="extensionType" ng-change="vm.extensionTypeChange()" ng-model="vm.extension.type">
@@ -55,16 +55,14 @@
                                 </md-select>
 
                                 <div ng-messages="theForm.extensionType.$error">
-                                    <div translate ng-message="required">extension.type-required</div>
+                                    <div translate ng-message="required">extension.field-required</div>
                                 </div>
                             </md-input-container>
                         </section>
-
-                        <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>
-
-                        <div tb-extension-form-opc  configuration="vm.extension.configuration"                   ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
+                        <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>
+                        <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>
+                        <div tb-extension-form-opc  configuration="vm.extension.configuration"  ng-if="vm.extension.type && vm.extension.type == vm.types.extensionType.opc"></div>
                     </fieldset>
-                    <!--<div>{{vm.extension}}</div>-->
                 </md-content>
             </div>
         </md-dialog-content>
@@ -80,5 +78,4 @@
             </md-button>
         </md-dialog-actions>
     </form>
-</md-dialog>
-
+</md-dialog>
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss
index 142516c..cc3e052 100644
--- a/ui/src/app/extension/extensions-forms/extension-form.scss
+++ b/ui/src/app/extension/extensions-forms/extension-form.scss
@@ -22,6 +22,23 @@
     margin-top: 0;
     padding-left: 3px;
   }
+  .tb-container {
+    width:100%;
+  }
+  .dropdown-messages {
+    .tb-error-message {
+      padding: 5px 0 0 0;
+    }
+  }
+  .dropdown-section {
+    margin-bottom: 30px;
+  }
+}
+
+.extension-form.extension-mqtt {
+  md-checkbox{
+    margin-left: 10px;
+  }
 }
 
 .tb-extension-custom-transformer-panel {
diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js
index 4c09078..9484ab4 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-http.directive.js
+++ b/ui/src/app/extension/extensions-forms/extension-form-http.directive.js
@@ -67,7 +67,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
             if (index > -1) {
                 scope.converterConfigs.splice(index, 1);
             }
-            scope.theForm.$setDirty();
         };
 
         scope.addConverter = function(converters) {
@@ -85,7 +84,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
             if (index > -1) {
                 converters.splice(index, 1);
             }
-            scope.theForm.$setDirty();
         };
 
         scope.addAttribute = function(attributes) {
@@ -98,13 +96,9 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
             if (index > -1) {
                 attributes.splice(index, 1);
             }
-            scope.theForm.$setDirty();
         };
 
 
-
-
-
         if(scope.isAdd) {
             scope.converterConfigs = scope.config.converterConfigurations;
             scope.addConverterConfig();
@@ -112,28 +106,6 @@ export default function ExtensionFormHttpDirective($compile, $templateCache, $tr
             scope.converterConfigs = scope.config.converterConfigurations;
         }
 
-
-
-        scope.updateValidity = function() {
-            let valid = scope.converterConfigs && scope.converterConfigs.length > 0;
-            scope.theForm.$setValidity('converterConfigs', valid);
-            if(scope.converterConfigs.length) {
-                for(let i=0; i<scope.converterConfigs.length; i++) {
-                    if(!scope.converterConfigs[i].converters.length) {
-                        scope.theForm.$setValidity('converters', false);
-                        break;
-                    } else {
-                        scope.theForm.$setValidity('converters', true);
-                    }
-                }
-            }
-        };
-
-        scope.$watch('converterConfigs', function() {
-            scope.updateValidity();
-        }, true);
-
-
         scope.transformerTypeChange = function(attribute) {
             attribute.transformer = "";
         };
diff --git a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html
index 6416656..8202d29 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-http.tpl.html
@@ -23,14 +23,11 @@
     </md-card-title>
     <md-card-content>
         <v-accordion id="http-converter-configs-accordion" class="vAccordion--default">
-            <v-pane id="http-converters-pane" expanded="isAdd">
+            <v-pane id="http-converters-pane" expanded="true">
                 <v-pane-header>
                     {{ 'extension.converter-configurations' | translate }}
                 </v-pane-header>
                 <v-pane-content>
-                    <div ng-if="converterConfigs.length === 0">
-                        <span translate layout-align="center center" class="tb-prompt">extension.add-config-prompt</span>
-                    </div>
                     <div ng-if="converterConfigs.length > 0">
                         <ol class="list-group">
                             <li class="list-group-item" ng-repeat="(configIndex, config) in converterConfigs">
@@ -47,11 +44,11 @@
                                 <md-card>
                                     <md-card-content>
 
-                                        <md-input-container class="md-block">
+                                        <md-input-container class="md-block" md-is-error="theForm['httpConverterId_' + configIndex].$touched && theForm['httpConverterId_' + configIndex].$invalid">
                                             <label translate>extension.converter-id</label>
                                             <input required name="httpConverterId_{{configIndex}}" ng-model="config.converterId">
                                             <div ng-messages="theForm['httpConverterId_' + configIndex].$error">
-                                                <div translate ng-message="required">extension.converter-id-required</div>
+                                                <div translate ng-message="required">extension.field-required</div>
                                             </div>
                                         </md-input-container>
                                         <md-input-container class="md-block">
@@ -59,14 +56,11 @@
                                             <input name="httpToken" ng-model="config.token" parse-to-null>
                                         </md-input-container>
                                         <v-accordion id="http-converters-accordion" class="vAccordion--default">
-                                            <v-pane id="http-converters-pane">
+                                            <v-pane id="http-converters-pane" expanded="true">
                                                 <v-pane-header>
                                                     {{ 'extension.converters' | translate }}
                                                 </v-pane-header>
                                                 <v-pane-content>
-                                                    <div ng-if="config.converters.length === 0">
-                                                        <span translate layout-align="center center" class="tb-prompt">extension.add-converter-prompt</span>
-                                                    </div>
                                                     <div ng-if="config.converters.length > 0">
                                                         <ol class="list-group">
                                                             <li class="list-group-item"
@@ -84,18 +78,18 @@
                                                                 </md-button>
                                                                 <md-card>
                                                                     <md-card-content>
-                                                                        <md-input-container class="md-block">
+                                                                        <md-input-container class="md-block" md-is-error="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceNameExp_' + configIndex + converterIndex].$invalid">
                                                                             <label translate>extension.device-name-expression</label>
                                                                             <input required name="httpDeviceNameExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceNameJsonExpression">
                                                                             <div ng-messages="theForm['httpDeviceNameExp_' + configIndex + converterIndex].$error">
-                                                                                <div translate ng-message="required">extension.device-name-expression-required</div>
+                                                                                <div translate ng-message="required">extension.field-required</div>
                                                                             </div>
                                                                         </md-input-container>
-                                                                        <md-input-container class="md-block">
+                                                                        <md-input-container class="md-block" md-is-error="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$touched && theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$invalid">
                                                                             <label translate>extension.device-type-expression</label>
                                                                             <input required name="httpDeviceTypeExp_{{configIndex}}{{converterIndex}}" ng-model="converter.deviceTypeJsonExpression">
                                                                             <div ng-messages="theForm['httpDeviceTypeExp_' + configIndex + converterIndex].$error">
-                                                                                <div translate ng-message="required">extension.device-type-expression-required</div>
+                                                                                <div translate ng-message="required">extension.field-required</div>
                                                                             </div>
                                                                         </md-input-container>
 
@@ -117,14 +111,14 @@
                                                                                                 <md-card>
                                                                                                     <md-card-content>
                                                                                                         <section flex layout="row">
-                                                                                                            <md-input-container flex="60" class="md-block">
+                                                                                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$invalid">
                                                                                                                 <label translate>extension.key</label>
                                                                                                                 <input required name="httpAttributeKey_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.key">
                                                                                                                 <div ng-messages="theForm['httpAttributeKey_' + configIndex + converterIndex + attributeIndex].$error">
-                                                                                                                    <div translate ng-message="required">extension.required-key</div>
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
-                                                                                                            <md-input-container flex="40" class="md-block">
+                                                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$invalid">
                                                                                                                 <label translate>extension.type</label>
                                                                                                                 <md-select required name="httpAttributeType_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.type">
                                                                                                                     <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -132,16 +126,16 @@
                                                                                                                     </md-option>
                                                                                                                 </md-select>
                                                                                                                 <div ng-messages="theForm['httpAttributeType_' + configIndex + converterIndex + attributeIndex].$error">
-                                                                                                                    <div translate ng-message="required">extension.required-type</div>
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
                                                                                                         </section>
                                                                                                         <section flex layout="row">
-                                                                                                            <md-input-container flex="60" class="md-block">
+                                                                                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$touched && theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$invalid">
                                                                                                                 <label translate>extension.value</label>
                                                                                                                 <input required name="httpAttributeValue_{{configIndex}}{{converterIndex}}{{attributeIndex}}" ng-model="attribute.value">
                                                                                                                 <div ng-messages="theForm['httpAttributeValue_' + configIndex + converterIndex + attributeIndex].$error">
-                                                                                                                    <div translate ng-message="required">extension.required-value</div>
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
 
@@ -182,11 +176,8 @@
                                                                                     <div flex layout="row" layout-align="start center">
                                                                                         <md-button class="md-primary md-raised"
                                                                                                    ng-click="addAttribute(converter.attributes)" aria-label="{{ 'action.add' | translate }}">
-                                                                                            <md-tooltip md-direction="top">
-                                                                                                {{ 'extension.add-attribute' | translate }}
-                                                                                            </md-tooltip>
                                                                                             <md-icon class="material-icons">add</md-icon>
-                                                                                            <span translate>action.add</span>
+                                                                                            <span translate>extension.add-attribute</span>
                                                                                         </md-button>
                                                                                     </div>
                                                                                 </v-pane-content>
@@ -212,14 +203,14 @@
                                                                                                 <md-card>
                                                                                                     <md-card-content>
                                                                                                         <section flex layout="row">
-                                                                                                            <md-input-container flex="60" class="md-block">
+                                                                                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$invalid">
                                                                                                                 <label translate>extension.key</label>
                                                                                                                 <input required name="httpTimeseriesKey_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
                                                                                                                 <div ng-messages="theForm['httpTimeseriesKey_' + configIndex + converterIndex + timeseriesIndex].$error">
-                                                                                                                    <div translate ng-message="required">extension.required-key</div>
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
-                                                                                                            <md-input-container flex="40" class="md-block">
+                                                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$invalid">
                                                                                                                 <label translate>extension.type</label>
                                                                                                                 <md-select required name="httpTimeseriesType_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
                                                                                                                     <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
@@ -227,16 +218,16 @@
                                                                                                                     </md-option>
                                                                                                                 </md-select>
                                                                                                                 <div ng-messages="theForm['httpTimeseriesType_' + configIndex + converterIndex + timeseriesIndex].$error">
-                                                                                                                    <div translate ng-message="required">extension.required-type</div>
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
                                                                                                         </section>
                                                                                                         <section flex layout="row">
-                                                                                                            <md-input-container flex="60" class="md-block">
+                                                                                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$touched && theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$invalid">
                                                                                                                 <label translate>extension.value</label>
                                                                                                                 <input required name="httpTimeseriesValue_{{configIndex}}{{converterIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
                                                                                                                 <div ng-messages="theForm['httpTimeseriesValue_' + configIndex + converterIndex + timeseriesIndex].$error">
-                                                                                                                    <div translate ng-message="required">extension.required-value</div>
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
 
@@ -277,11 +268,8 @@
                                                                                     <div flex layout="row" layout-align="start center">
                                                                                         <md-button class="md-primary md-raised"
                                                                                                    ng-click="addAttribute(converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
-                                                                                            <md-tooltip md-direction="top">
-                                                                                                {{ 'extension.add-timeseries' | translate }}
-                                                                                            </md-tooltip>
                                                                                             <md-icon class="material-icons">add</md-icon>
-                                                                                            <span translate>action.add</span>
+                                                                                            <span translate>extension.add-timeseries</span>
                                                                                         </md-button>
                                                                                     </div>
                                                                                 </v-pane-content>
@@ -295,11 +283,8 @@
                                                     <div flex layout="row" layout-align="start center">
                                                         <md-button class="md-primary md-raised"
                                                                    ng-click="addConverter(config.converters)" aria-label="{{ 'action.add' | translate }}">
-                                                            <md-tooltip md-direction="top">
-                                                                {{ 'extension.add-converter' | translate }}
-                                                            </md-tooltip>
                                                             <md-icon class="material-icons">add</md-icon>
-                                                            <span translate>action.add</span>
+                                                            <span translate>extension.add-converter</span>
                                                         </md-button>
                                                     </div>
                                                 </v-pane-content>
@@ -314,11 +299,8 @@
                     <div flex layout="row" layout-align="start center">
                         <md-button class="md-primary md-raised"
                                    ng-click="addConverterConfig()" aria-label="{{ 'action.add' | translate }}">
-                            <md-tooltip md-direction="top">
-                                {{ 'extension.add-config' | translate }}
-                            </md-tooltip>
                             <md-icon class="material-icons">add</md-icon>
-                            <span translate>action.add</span>
+                            <span translate>extension.add-config</span>
                         </md-button>
                     </div>
                 </v-pane-content>
diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js b/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js
new file mode 100644
index 0000000..41e9aad
--- /dev/null
+++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.directive.js
@@ -0,0 +1,335 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './extension-form.scss';
+
+/* eslint-disable angular/log */
+
+import extensionFormMqttTemplate from './extension-form-mqtt.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ExtensionFormHttpDirective($compile, $templateCache, $translate, types) {
+
+    var linker = function(scope, element) {
+
+        var template = $templateCache.get(extensionFormMqttTemplate);
+        element.html(template);
+
+        scope.types = types;
+        scope.theForm = scope.$parent.theForm;
+
+        scope.deviceNameExpressions = {
+            deviceNameJsonExpression: "extension.converter-json",
+            deviceNameTopicExpression: "extension.topic"
+        };
+        scope.deviceTypeExpressions = {
+            deviceTypeJsonExpression: "extension.converter-json",
+            deviceTypeTopicExpression: "extension.topic"
+        };
+        scope.attributeKeyExpressions = {
+            attributeKeyJsonExpression: "extension.converter-json",
+            attributeKeyTopicExpression: "extension.topic"
+        };
+        scope.requestIdExpressions = {
+            requestIdJsonExpression: "extension.converter-json",
+            requestIdTopicExpression: "extension.topic"
+        }
+
+        scope.extensionCustomConverterOptions = {
+            useWrapMode: false,
+            mode: 'json',
+            showGutter: true,
+            showPrintMargin: true,
+            theme: 'github',
+            advanced: {
+                enableSnippets: true,
+                enableBasicAutocompletion: true,
+                enableLiveAutocompletion: true
+            },
+            onLoad: function(_ace) {
+                _ace.$blockScrolling = 1;
+            }
+        };
+
+        scope.updateValidity = function () {
+            if(scope.brokers.length) {
+                for(let i=0;i<scope.brokers.length;i++) {
+                    if(scope.brokers[i].credentials.type == scope.types.mqttCredentialTypes.pem.value) {
+                        if(!(scope.brokers[i].credentials.caCert && scope.brokers[i].credentials.privateKey && scope.brokers[i].credentials.cert)) {
+                            scope.theForm.$setValidity('cert.PEM', false);
+                            break;
+                        } else {
+                            scope.theForm.$setValidity('cert.PEM', true);
+                        }
+                    }
+                }
+            }
+        };
+
+        scope.$watch('brokers', function() {
+            scope.updateValidity();
+        }, true);
+
+        scope.addBroker = function() {
+            var newBroker = {
+                host: "localhost",
+                port: 1882,
+                ssl: false,
+                retryInterval: 3000,
+                credentials: {type:"anonymous"},
+                mapping: [],
+                connectRequests: [],
+                disconnectRequests: [],
+                attributeRequests: [],
+                attributeUpdates: [],
+                serverSideRpc: []
+            };
+            scope.brokers.push(newBroker);
+        };
+
+        scope.removeBroker = function(broker) {
+            var index = scope.brokers.indexOf(broker);
+            if (index > -1) {
+                scope.brokers.splice(index, 1);
+            }
+        };
+
+        if(scope.isAdd) {
+            scope.brokers = [];
+            scope.config.brokers = scope.brokers;
+            scope.addBroker();
+        } else {
+            scope.brokers = scope.config.brokers;
+        }
+
+        scope.addMap = function(mapping) {
+            var newMap = {topicFilter:"sensors", converter:{attributes:[],timeseries:[]}};
+
+            mapping.push(newMap);
+        };
+
+        scope.removeMap = function(map, mapping) {
+            var index = mapping.indexOf(map);
+            if (index > -1) {
+                mapping.splice(index, 1);
+            }
+        };
+
+        scope.addAttribute = function(attributes) {
+            var newAttribute = {type:"", key:"", value:""};
+            attributes.push(newAttribute);
+        };
+
+        scope.removeAttribute = function(attribute, attributes) {
+            var index = attributes.indexOf(attribute);
+            if (index > -1) {
+                attributes.splice(index, 1);
+            }
+        };
+
+        scope.addConnectRequest = function(requests, type) {
+            var newRequest = {};
+            if(type == "connect") {
+                newRequest.topicFilter = "sensors/connect";
+            } else {
+                newRequest.topicFilter = "sensors/disconnect";
+            }
+            requests.push(newRequest);
+        };
+
+        scope.addAttributeRequest = function(requests) {
+            var newRequest = {
+                topicFilter: "sensors/attributes",
+                clientScope: false,
+                responseTopicExpression: "sensors/${deviceName}/attributes/${responseId}",
+                valueExpression: "${attributeValue}"
+            };
+            requests.push(newRequest);
+        };
+
+        scope.addAttributeUpdate = function(updates) {
+            var newUpdate = {
+                deviceNameFilter: ".*",
+                attributeFilter: ".*",
+                topicExpression: "sensor/${deviceName}/${attributeKey}",
+                valueExpression: "{\"${attributeKey}\":\"${attributeValue}\"}"
+            }
+            updates.push(newUpdate);
+        };
+
+        scope.addServerSideRpc = function(rpcRequests) {
+            var newRpc = {
+                deviceNameFilter: ".*",
+                methodFilter: "echo",
+                requestTopicExpression: "sensor/${deviceName}/request/${methodName}/${requestId}",
+                responseTopicExpression: "sensor/${deviceName}/response/${methodName}/${requestId}",
+                responseTimeout: 10000,
+                valueExpression: "${params}"
+            };
+            rpcRequests.push(newRpc);
+        };
+
+        scope.changeCredentials = function(broker) {
+            var type = broker.credentials.type;
+            broker.credentials = {};
+            broker.credentials.type = type;
+        };
+
+        scope.changeConverterType = function(map) {
+            if(map.converterType == "custom"){
+                map.converter = "";
+            }
+            if(map.converterType == "json") {
+                map.converter = {attributes:[],timeseries:[]};
+            }
+        };
+
+        scope.changeNameExpression = function(element, type) {
+            if(element.nameExp == "deviceNameJsonExpression") {
+                if(element.deviceNameTopicExpression) {
+                    delete element.deviceNameTopicExpression;
+                }
+                if(type) {
+                    element.deviceNameJsonExpression = "${$.serialNumber}";
+                }
+            }
+            if(element.nameExp == "deviceNameTopicExpression") {
+                if(element.deviceNameJsonExpression) {
+                    delete element.deviceNameJsonExpression;
+                }
+                if(type && type == "connect") {
+                    element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/connect)";
+                }
+                if(type && type == "disconnect") {
+                    element.deviceNameTopicExpression = "(?<=sensor\\/)(.*?)(?=\\/disconnect)";
+                }
+                if(type && type == "attribute") {
+                    element.deviceNameTopicExpression = "(?<=sensors\\/)(.*?)(?=\\/attributes)";
+                }
+            }
+        };
+
+        scope.changeTypeExpression = function(converter) {
+            if(converter.typeExp == "deviceTypeJsonExpression") {
+                if(converter.deviceTypeTopicExpression) {
+                    delete converter.deviceTypeTopicExpression;
+                }
+            }
+            if(converter.typeExp == "deviceTypeTopicExpression") {
+                if(converter.deviceTypeJsonExpression) {
+                    delete converter.deviceTypeJsonExpression;
+                }
+            }
+        };
+
+        scope.changeAttrKeyExpression = function(request) {
+            if(request.attrKey == "attributeKeyJsonExpression") {
+                if(request.attributeKeyTopicExpression) {
+                    delete request.attributeKeyTopicExpression;
+                }
+                request.attributeKeyJsonExpression = "${$.key}";
+            }
+            if(request.attrKey == "attributeKeyTopicExpression") {
+                if(request.attributeKeyJsonExpression) {
+                    delete request.attributeKeyJsonExpression;
+                }
+                request.attributeKeyTopicExpression = "(?<=attributes\\/)(.*?)(?=\\/request)";
+            }
+        };
+
+        scope.changeRequestIdExpression = function(request) {
+            if(request.requestId == "requestIdJsonExpression") {
+                if(request.requestIdTopicExpression) {
+                    delete request.requestIdTopicExpression;
+                }
+                request.requestIdJsonExpression = "${$.requestId}";
+            }
+            if(request.requestId == "requestIdTopicExpression") {
+                if(request.requestIdJsonExpression) {
+                    delete request.requestIdJsonExpression;
+                }
+                request.requestIdTopicExpression = "(?<=request\\/)(.*?)($)";
+            }
+        };
+
+        scope.validateCustomConverter = function(model, editorName) {
+            if(model && model.length) {
+                try {
+                    angular.fromJson(model);
+                    scope.theForm[editorName].$setValidity('converterJSON', true);
+                } catch(e) {
+                    scope.theForm[editorName].$setValidity('converterJSON', false);
+                }
+            }
+        };
+
+        scope.fileAdded = function($file, broker, fileType) {
+            var reader = new FileReader();
+            reader.onload = function(event) {
+                scope.$apply(function() {
+                    if(event.target.result) {
+                        scope.theForm.$setDirty();
+                        var addedFile = event.target.result;
+                        if (addedFile && addedFile.length > 0) {
+                            if(fileType == "caCert") {
+                                broker.credentials.caCertFileName = $file.name;
+                                broker.credentials.caCert = addedFile.replace(/^data.*base64,/, "");
+                            }
+                            if(fileType == "privateKey") {
+                                broker.credentials.privateKeyFileName = $file.name;
+                                broker.credentials.privateKey = addedFile.replace(/^data.*base64,/, "");
+                            }
+                            if(fileType == "Cert") {
+                                broker.credentials.certFileName = $file.name;
+                                broker.credentials.cert = addedFile.replace(/^data.*base64,/, "");
+                            }
+                        }
+                    }
+                });
+            };
+            reader.readAsDataURL($file.file);
+        };
+
+        scope.clearFile = function(broker, fileType) {
+            scope.theForm.$setDirty();
+            if(fileType == "caCert") {
+                broker.credentials.caCertFileName = null;
+                broker.credentials.caCert = null;
+            }
+            if(fileType == "privateKey") {
+                broker.credentials.privateKeyFileName = null;
+                broker.credentials.privateKey = null;
+            }
+            if(fileType == "Cert") {
+                broker.credentials.certFileName = null;
+                broker.credentials.cert = null;
+            }
+        };
+
+        $compile(element.contents())(scope);
+    };
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            config: "=",
+            isAdd: "="
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
index e55ad6c..7950c75 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
@@ -15,4 +15,846 @@
     limitations under the License.
 
 -->
-<div>MQTT</div>
\ No newline at end of file
+<md-card class="extension-form extension-mqtt">
+    <md-card-title name="testValid">
+        <md-card-title-text>
+            <span translate class="md-headline">extension.configuration</span>
+        </md-card-title-text>
+    </md-card-title>
+    <md-card-content>
+        <v-accordion id="mqtt-brokers-accordion" class="vAccordion--default">
+            <v-pane id="mqtt-brokers-pane" expanded="true">
+                <v-pane-header>
+                    {{ 'extension.brokers' | translate }}
+                </v-pane-header>
+                <v-pane-content>
+                    <div ng-if="brokers.length > 0">
+                        <ol class="list-group">
+                            <li class="list-group-item" ng-repeat="(brokerIndex,broker) in brokers">
+                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeBroker(broker)" ng-hide="brokers.length < 2">
+                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                    <md-tooltip md-direction="top">
+                                        {{ 'action.remove' | translate }}
+                                    </md-tooltip>
+                                </md-button>
+                                <md-card>
+                                    <md-card-content>
+                                        <section flex layout="row">
+                                            <md-input-container flex="40" class="md-block">
+                                                <label translate>extension.port</label>
+                                                <input required type="number" min="1" max="65535" name="mqttPort_{{brokerIndex}}" ng-model="broker.port">
+                                                <div ng-messages="theForm['mqttPort_' + brokerIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                    <div translate ng-message="min">extension.port-range</div>
+                                                    <div translate ng-message="max">extension.port-range</div>
+                                                </div>
+                                            </md-input-container>
+                                            <md-input-container flex="60" class="md-block">
+                                                <label translate>extension.host</label>
+                                                <input required name="mqttHost_{{brokerIndex}}" ng-model="broker.host">
+                                                <div ng-messages="theForm['mqttHost_' + brokerIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                        </section>
+                                        <section flex layout="row">
+                                            <md-input-container flex="40" class="md-block">
+                                                <label translate>extension.retry-interval</label>
+                                                <input required type="number" name="mqttRetryInterval_{{brokerIndex}}" ng-model="broker.retryInterval">
+                                                <div ng-messages="theForm['mqttRetryInterval_' + brokerIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                            <md-input-container flex="50" class="md-block">
+                                                <label translate>extension.credentials</label>
+                                                <md-select required name="mqttCredentials_{{brokerIndex}}" ng-model="broker.credentials.type" ng-change="changeCredentials(broker)">
+                                                    <md-option ng-repeat="(credentialsType, credentialsValue) in types.mqttCredentialTypes" ng-value="credentialsValue.value">
+                                                        {{credentialsValue.name | translate}}
+                                                    </md-option>
+                                                </md-select>
+                                            </md-input-container>
+                                            <md-input-container flex="10" class="md-block">
+                                                <md-checkbox flex aria-label="{{ 'extension.ssl' | translate }}"
+                                                             ng-model="broker.ssl">{{ 'extension.ssl' | translate }}
+                                                </md-checkbox>
+                                            </md-input-container>
+                                        </section>
+                                        <section flex layout="row" ng-if='broker.credentials.type == "basic"'>
+                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttUsername_' + brokerIndex].$touched && theForm['mqttUsername_' + brokerIndex].$invalid">
+                                                <label translate>extension.username</label>
+                                                <input required name="mqttUsername_{{brokerIndex}}" ng-model="broker.credentials.username">
+                                                <div ng-messages="theForm['mqttUsername_' + brokerIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttPassword_' + brokerIndex].$touched && theForm['mqttPassword_' + brokerIndex].$invalid">
+                                                <label translate>extension.password</label>
+                                                <input required name="mqttPassword_{{brokerIndex}}" ng-model="broker.credentials.password">
+                                                <div ng-messages="theForm['mqttPassword_' + brokerIndex].$error">
+                                                    <div translate ng-message="required">extension.field-required</div>
+                                                </div>
+                                            </md-input-container>
+                                        </section>
+                                        <section flex layout="column" ng-if='broker.credentials.type == "cert.PEM"' class="dropdown-section">
+                                            <div class="tb-container" ng-class="broker.credentials.caCertFileName ? 'ng-valid' : 'ng-invalid'">
+                                                <label class="tb-label" translate>extension.ca-cert</label>
+                                                <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "caCert")' class="tb-file-select-container">
+                                                    <div class="tb-file-clear-container">
+                                                        <md-button ng-click='clearFile(broker, "caCert")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
+                                                            <md-tooltip md-direction="top">
+                                                                {{ 'action.remove' | translate }}
+                                                            </md-tooltip>
+                                                            <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
+                                                        </md-button>
+                                                    </div>
+                                                    <div class="alert tb-flow-drop" flow-drop>
+                                                        <label for="caCertSelect_{{brokerIndex}}" translate>extension.drop-file</label>
+                                                        <input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="caCertSelect_{{brokerIndex}}">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="dropdown-messages">
+                                                <div ng-if="!broker.credentials.caCertFileName" class="tb-error-message" translate>extension.no-file</div>
+                                                <div ng-if="broker.credentials.caCertFileName">{{broker.credentials.caCertFileName}}</div>
+                                            </div>
+                                            <div class="tb-container" ng-class="broker.credentials.privateKeyFileName ? 'ng-valid' : 'ng-invalid'">
+                                                <label class="tb-label" translate>extension.private-key</label>
+                                                <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "privateKey")' class="tb-file-select-container">
+                                                    <div class="tb-file-clear-container">
+                                                        <md-button ng-click='clearFile(broker, "privateKey")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
+                                                            <md-tooltip md-direction="top">
+                                                                {{ 'action.remove' | translate }}
+                                                            </md-tooltip>
+                                                            <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
+                                                        </md-button>
+                                                    </div>
+                                                    <div class="alert tb-flow-drop" flow-drop>
+                                                        <label for="privateKeySelect_{{brokerIndex}}" translate>extension.drop-file</label>
+                                                        <input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="privateKeySelect_{{brokerIndex}}">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="dropdown-messages">
+                                                <div ng-if="!broker.credentials.privateKeyFileName" class="tb-error-message" translate>extension.no-file</div>
+                                                <div ng-if="broker.credentials.privateKeyFileName">{{broker.credentials.privateKeyFileName}}</div>
+                                            </div>
+                                            <div class="tb-container" ng-class="broker.credentials.certFileName ? 'ng-valid' : 'ng-invalid'">
+                                                <label class="tb-label" translate>extension.cert</label>
+                                                <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, broker, "Cert")' class="tb-file-select-container">
+                                                    <div class="tb-file-clear-container">
+                                                        <md-button ng-click='clearFile(broker, "Cert")' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
+                                                            <md-tooltip md-direction="top">
+                                                                {{ 'action.remove' | translate }}
+                                                            </md-tooltip>
+                                                            <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
+                                                        </md-button>
+                                                    </div>
+                                                    <div class="alert tb-flow-drop" flow-drop>
+                                                        <label for="CertSelect_{{brokerIndex}}" translate>extension.drop-file</label>
+                                                        <input class="file-input" flow-btn flow-attrs="{accept:'.pem'}" id="CertSelect_{{brokerIndex}}">
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="dropdown-messages">
+                                                <div ng-if="!broker.credentials.certFileName" class="tb-error-message" translate>extension.no-file</div>
+                                                <div ng-if="broker.credentials.certFileName">{{broker.credentials.certFileName}}</div>
+                                            </div>
+                                        </section>
+
+                                        <v-accordion id="mqtt-mapping-accordion" class="vAccordion--default">
+                                            <v-pane id="mqtt-mapping-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.mapping' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="broker.mapping.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item" ng-repeat="(mapIndex,map) in broker.mapping">
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeMap(map, broker.mapping)">
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttConverterType_' + brokerIndex + mapIndex].$touched && theForm['mqttConverterType_' + brokerIndex + mapIndex].$invalid">
+                                                                                <label translate>extension.converter-type</label>
+                                                                                <md-select required name="mqttConverterType_{{brokerIndex}}{{mapIndex}}" ng-model="map.converterType" ng-change="changeConverterType(map)">
+                                                                                    <md-option ng-repeat="(converterType, value) in types.mqttConverterTypes" ng-value="converterType">
+                                                                                        {{value | translate}}
+                                                                                    </md-option>
+                                                                                </md-select>
+                                                                                <div ng-messages="theForm['mqttConverterType_' + brokerIndex + mapIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container flex="60" class="md-block">
+                                                                                <label translate>extension.topic-filter</label>
+                                                                                <input required name="mqttTopicFilter_{{brokerIndex}}{{mapIndex}}" ng-model="map.topicFilter">
+                                                                                <div ng-messages="theForm['mqttTopicFilter_' + brokerIndex + mapIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+
+                                                                        <div ng-if='map.converterType =="json"' ng-init="map.converter.type = 'json'">
+                                                                            <section flex layout="row">
+                                                                                <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$touched && theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$invalid">
+                                                                                    <label translate>extension.device-name-expression</label>
+                                                                                    <md-select required name="mqttDeviceNameExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.nameExp" ng-change="changeNameExpression(map.converter)">
+                                                                                        <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
+                                                                                            {{value | translate}}
+                                                                                        </md-option>
+                                                                                    </md-select>
+                                                                                    <div ng-messages="theForm['mqttDeviceNameExpression_' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                                <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">
+                                                                                    <label translate>extension.json-name-expression</label>
+                                                                                    <input required name="mqttJsonNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameJsonExpression">
+                                                                                    <div ng-messages="theForm['mqttJsonNameExp_' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                                <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">
+                                                                                    <label translate>extension.topic-name-expression</label>
+                                                                                    <input required name="mqttTopicNameExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceNameTopicExpression">
+                                                                                    <div ng-messages="theForm['mqttTopicNameExp_' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                            </section>
+                                                                            <section flex layout="row">
+                                                                                <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$touched && theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$invalid">
+                                                                                    <label translate>extension.device-type-expression</label>
+                                                                                    <md-select required name="mqttDeviceTypeExpression_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.typeExp" ng-change="changeTypeExpression(map.converter)">
+                                                                                        <md-option ng-repeat="(key, value) in deviceTypeExpressions" ng-value='key'>
+                                                                                            {{value | translate}}
+                                                                                        </md-option>
+                                                                                    </md-select>
+                                                                                    <div ng-messages="theForm['mqttDeviceTypeExpression_' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                                <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">
+                                                                                    <label translate>extension.json-type-expression</label>
+                                                                                    <input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression">
+                                                                                    <div ng-messages="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                                <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">
+                                                                                    <label translate>extension.topic-type-expression</label>
+                                                                                    <input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression">
+                                                                                    <div ng-messages="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                            </section>
+                                                                            <section flex layout="row">
+                                                                                <md-input-container flex="40" class="md-block">
+                                                                                    <label translate>extension.timeout</label>
+                                                                                    <input type="number" name="mqttTimeout_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.timeout" parse-to-null>
+                                                                                </md-input-container>
+                                                                                <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$touched && theForm['mqttFilterExpression' + brokerIndex + mapIndex].$invalid">
+                                                                                    <label translate>extension.filter-expression</label>
+                                                                                    <input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
+                                                                                    <div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error">
+                                                                                        <div translate ng-message="required">extension.field-required</div>
+                                                                                    </div>
+                                                                                </md-input-container>
+                                                                            </section>
+                                                                        </div>
+
+                                                                        <div ng-if='map.converterType == "custom"'>
+                                                                            <div class="md-caption" style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>extension.transformer-json</div>
+                                                                            <div flex class="tb-extension-custom-transformer-panel">
+                                                                                <div flex class="tb-extension-custom-transformer"
+                                                                                     ui-ace="extensionCustomConverterOptions"
+                                                                                     ng-model="map.converter"
+                                                                                     name="mqttCustomConverter_{{brokerIndex}}{{mapIndex}}"
+                                                                                     ng-change='validateCustomConverter(map.converter, "mqttCustomConverter_" + brokerIndex + mapIndex)'
+                                                                                     required>
+                                                                                </div>
+                                                                            </div>
+                                                                            <div class="tb-error-messages" ng-messages="theForm['mqttCustomConverter_' + brokerIndex + mapIndex].$error" role="alert">
+                                                                                <div ng-message="required" class="tb-error-message" translate>extension.converter-json-required</div>
+                                                                                <div ng-message="converterJSON" class="tb-error-message" translate>extension.converter-json-parse</div>
+                                                                            </div>
+                                                                        </div>
+
+                                                                        <v-accordion ng-if='map.converterType =="json"' id="mqtt-attributes-accordion" class="vAccordion--default">
+                                                                            <v-pane id="mqtt-attributes-pane">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.attributes' | translate }}
+                                                                                </v-pane-header>
+                                                                                <v-pane-content>
+                                                                                    <div ng-if="map.converter.attributes.length > 0">
+                                                                                        <ol class="list-group">
+                                                                                            <li class="list-group-item" ng-repeat="(attributeIndex, attribute) in map.converter.attributes">
+                                                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attribute, map.converter.attributes)">
+                                                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                                                    <md-tooltip md-direction="top">
+                                                                                                        {{ 'action.remove' | translate }}
+                                                                                                    </md-tooltip>
+                                                                                                </md-button>
+                                                                                                <md-card>
+                                                                                                    <md-card-content>
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$invalid">
+                                                                                                                <label translate>extension.key</label>
+                                                                                                                <input required name="mqttAttributeKey_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.key">
+                                                                                                                <div ng-messages="theForm['mqttAttributeKey_' + brokerIndex + mapIndex + attributeIndex].$error">
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$invalid">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="mqttAttributeType_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.type">
+                                                                                                                    <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
+                                                                                                                        {{attrTypeValue | translate}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['mqttAttributeType_' + brokerIndex + mapIndex + attributeIndex].$error">
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        <md-input-container class="md-block" md-is-error="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$touched && theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$invalid">
+                                                                                                            <label translate>extension.value</label>
+                                                                                                            <input required name="mqttAttributeValue_{{brokerIndex}}{{mapIndex}}{{attributeIndex}}" ng-model="attribute.value">
+                                                                                                            <div ng-messages="theForm['mqttAttributeValue_' + brokerIndex + mapIndex + attributeIndex].$error">
+                                                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                                                            </div>
+                                                                                                        </md-input-container>
+                                                                                                    </md-card-content>
+                                                                                                </md-card>
+                                                                                            </li>
+                                                                                        </ol>
+                                                                                    </div>
+                                                                                    <div flex layout="row" layout-align="start center">
+                                                                                        <md-button class="md-primary md-raised"
+                                                                                                   ng-click="addAttribute(map.converter.attributes)" aria-label="{{ 'action.add' | translate }}">
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>extension.add-attribute</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+                                                                        <v-accordion ng-if='map.converterType =="json"' id="mqtt-timeseries-accordion" class="vAccordion--default">
+                                                                            <v-pane id="mqtt-timeseries-pane">
+                                                                                <v-pane-header>
+                                                                                    {{ 'extension.timeseries' | translate }}
+                                                                                </v-pane-header>
+                                                                                <v-pane-content>
+                                                                                    <div ng-if="map.converter.timeseries.length > 0">
+                                                                                        <ol class="list-group">
+                                                                                            <li class="list-group-item" ng-repeat="(timeseriesIndex, timeseries) in map.converter.timeseries">
+                                                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(timeseries, map.converter.timeseries)">
+                                                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                                                    <md-tooltip md-direction="top">
+                                                                                                        {{ 'action.remove' | translate }}
+                                                                                                    </md-tooltip>
+                                                                                                </md-button>
+                                                                                                <md-card>
+                                                                                                    <md-card-content>
+                                                                                                        <section flex layout="row">
+                                                                                                            <md-input-container flex="60" class="md-block" md-is-error="theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$invalid">
+                                                                                                                <label translate>extension.key</label>
+                                                                                                                <input required name="mqttTimeseriesKey_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.key">
+                                                                                                                <div ng-messages="theForm['mqttTimeseriesKey_' + brokerIndex + mapIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$invalid">
+                                                                                                                <label translate>extension.type</label>
+                                                                                                                <md-select required name="mqttTimeseriesType_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.type">
+                                                                                                                    <md-option ng-repeat="(attrType, attrTypeValue) in types.extensionValueType" ng-value="attrType">
+                                                                                                                        {{attrTypeValue | translate}}
+                                                                                                                    </md-option>
+                                                                                                                </md-select>
+                                                                                                                <div ng-messages="theForm['mqttTimeseriesType_' + brokerIndex + mapIndex + timeseriesIndex].$error">
+                                                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                                                </div>
+                                                                                                            </md-input-container>
+                                                                                                        </section>
+                                                                                                        <md-input-container class="md-block" md-is-error="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$touched && theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$invalid">
+                                                                                                            <label translate>extension.value</label>
+                                                                                                            <input required name="mqttTimeseriesValue_{{brokerIndex}}{{mapIndex}}{{timeseriesIndex}}" ng-model="timeseries.value">
+                                                                                                            <div ng-messages="theForm['mqttTimeseriesValue_' + brokerIndex + mapIndex + timeseriesIndex].$error">
+                                                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                                                            </div>
+                                                                                                        </md-input-container>
+                                                                                                    </md-card-content>
+                                                                                                </md-card>
+                                                                                            </li>
+                                                                                        </ol>
+                                                                                    </div>
+                                                                                    <div flex layout="row" layout-align="start center">
+                                                                                        <md-button class="md-primary md-raised"
+                                                                                                   ng-click="addAttribute(map.converter.timeseries)" aria-label="{{ 'action.add' | translate }}">
+                                                                                            <md-icon class="material-icons">add</md-icon>
+                                                                                            <span translate>extension.add-timeseries</span>
+                                                                                        </md-button>
+                                                                                    </div>
+                                                                                </v-pane-content>
+                                                                            </v-pane>
+                                                                        </v-accordion>
+
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex layout="row" layout-align="start center">
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click="addMap(broker.mapping)" aria-label="{{ 'action.add' | translate }}">
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-map</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                        <v-accordion id="mqtt-connect-requests-accordion" class="vAccordion--default">
+                                            <v-pane id="mqtt-connect-requests-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.connect-requests' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="broker.connectRequests.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item" ng-repeat="(connectRequestIndex, connectRequest) in broker.connectRequests">
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(connectRequest, broker.connectRequests)">
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.topic-filter</label>
+                                                                            <input required name="conRequestTopicFilter_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.topicFilter">
+                                                                            <div ng-messages="theForm['conRequestTopicFilter_' + brokerIndex + connectRequestIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$touched && theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$invalid">
+                                                                                <label translate>extension.device-name-expression</label>
+                                                                                <md-select required name="connectDeviceNameExpression_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.nameExp" ng-change="changeNameExpression(connectRequest, 'connect')">
+                                                                                    <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
+                                                                                        {{value | translate}}
+                                                                                    </md-option>
+                                                                                </md-select>
+                                                                                <div ng-messages="theForm['connectDeviceNameExpression_' + brokerIndex + connectRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="connectRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.json-name-expression</label>
+                                                                                <input required name="connectJsonNameExp_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.deviceNameJsonExpression">
+                                                                                <div ng-messages="theForm['connectJsonNameExp_' + brokerIndex + connectRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="connectRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.topic-name-expression</label>
+                                                                                <input required name="connectTopicNameExp_{{brokerIndex}}{{connectRequestIndex}}" ng-model="connectRequest.deviceNameTopicExpression">
+                                                                                <div ng-messages="theForm['connectTopicNameExp_' + brokerIndex + connectRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex layout="row" layout-align="start center">
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click='addConnectRequest(broker.connectRequests, "connect")' aria-label="{{ 'action.add' | translate }}">
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-connect-request</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                        <v-accordion id="mqtt-disconnect-requests-accordion" class="vAccordion--default">
+                                            <v-pane id="mqtt-disconnect-requests-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.disconnect-requests' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="broker.disconnectRequests.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item" ng-repeat="(disconnectRequestIndex, disconnectRequest) in broker.disconnectRequests">
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(disconnectRequest, broker.disconnectRequests)">
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.topic-filter</label>
+                                                                            <input required name="disconRequestTopicFilter_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.topicFilter">
+                                                                            <div ng-messages="theForm['disconRequestTopicFilter_' + brokerIndex + disconnectRequestIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$touched && theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$invalid">
+                                                                                <label translate>extension.device-name-expression</label>
+                                                                                <md-select required name="disconnectDeviceNameExpression_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.nameExp" ng-change="changeNameExpression(disconnectRequest, 'disconnect')">
+                                                                                    <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
+                                                                                        {{value | translate}}
+                                                                                    </md-option>
+                                                                                </md-select>
+                                                                                <div ng-messages="theForm['disconnectDeviceNameExpression_' + brokerIndex + disconnectRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="disconnectRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.json-name-expression</label>
+                                                                                <input required name="disconnectJsonNameExp_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.deviceNameJsonExpression">
+                                                                                <div ng-messages="theForm['disconnectJsonNameExp_' + brokerIndex + disconnectRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="disconnectRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.topic-name-expression</label>
+                                                                                <input required name="disconnectTopicNameExp_{{brokerIndex}}{{disconnectRequestIndex}}" ng-model="disconnectRequest.deviceNameTopicExpression">
+                                                                                <div ng-messages="theForm['disconnectTopicNameExp_' + brokerIndex + disconnectRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex layout="row" layout-align="start center">
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click='addConnectRequest(broker.disconnectRequests, "disconnect")' aria-label="{{ 'action.add' | translate }}">
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-disconnect-request</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                        <v-accordion id="mqtt-attribute-requests-accordion" class="vAccordion--default">
+                                            <v-pane id="mqtt-attribute-requests-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.attribute-requests' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="broker.attributeRequests.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item" ng-repeat="(attributeRequestIndex, attributeRequest) in broker.attributeRequests">
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attributeRequest, broker.attributeRequests)">
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="80" class="md-block">
+                                                                                <label translate>extension.topic-filter</label>
+                                                                                <input required name="attributeRequestTopicFilter_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.topicFilter">
+                                                                                <div ng-messages="theForm['attributeRequestTopicFilter_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container flex="20" class="md-block">
+                                                                                <md-checkbox flex aria-label="{{ 'extension.client-scope' | translate }}"
+                                                                                             ng-model="attributeRequest.clientScope">{{ 'extension.client-scope' | translate }}
+                                                                                </md-checkbox>
+                                                                            </md-input-container>
+                                                                        </section>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$invalid">
+                                                                                <label translate>extension.device-name-expression</label>
+                                                                                <md-select required name="attrRequestDeviceNameExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.nameExp" ng-change="changeNameExpression(attributeRequest, 'attribute')">
+                                                                                    <md-option ng-repeat="(key, value) in deviceNameExpressions" ng-value='key'>
+                                                                                        {{value | translate}}
+                                                                                    </md-option>
+                                                                                </md-select>
+                                                                                <div ng-messages="theForm['attrRequestDeviceNameExpression_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="attributeRequest.nameExp == 'deviceNameJsonExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.json-name-expression</label>
+                                                                                <input required name="attrRequestJsonNameExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.deviceNameJsonExpression">
+                                                                                <div ng-messages="theForm['attrRequestJsonNameExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="attributeRequest.nameExp == 'deviceNameTopicExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.topic-name-expression</label>
+                                                                                <input required name="attrRequestTopicNameExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.deviceNameTopicExpression">
+                                                                                <div ng-messages="theForm['attrRequestTopicNameExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$invalid">
+                                                                                <label translate>extension.attribute-key-expression</label>
+                                                                                <md-select required name="attrRequestAttributeKeyExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attrKey" ng-change="changeAttrKeyExpression(attributeRequest)">
+                                                                                    <md-option ng-repeat="(key, value) in attributeKeyExpressions" ng-value='key'>
+                                                                                        {{value | translate}}
+                                                                                    </md-option>
+                                                                                </md-select>
+                                                                                <div ng-messages="theForm['attrRequestAttributeKeyExpression_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="attributeRequest.attrKey == 'attributeKeyJsonExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.attr-json-key-expression</label>
+                                                                                <input required name="attrRequestJsonKeyExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attributeKeyJsonExpression">
+                                                                                <div ng-messages="theForm['attrRequestJsonKeyExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="attributeRequest.attrKey == 'attributeKeyTopicExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.attr-topic-key-expression</label>
+                                                                                <input required name="attrRequestTopicKeyExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.attributeKeyTopicExpression">
+                                                                                <div ng-messages="theForm['attrRequestTopicKeyExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="40" class="md-block" md-is-error="theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$touched && theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$invalid">
+                                                                                <label translate>extension.request-id-expression</label>
+                                                                                <md-select required name="attrRequestIdExpression_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestId" ng-change="changeRequestIdExpression(attributeRequest)">
+                                                                                    <md-option ng-repeat="(key, value) in requestIdExpressions" ng-value='key'>
+                                                                                        {{value | translate}}
+                                                                                    </md-option>
+                                                                                </md-select>
+                                                                                <div ng-messages="theForm['attrRequestIdExpression_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="attributeRequest.requestId == 'requestIdJsonExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.request-id-json-expression</label>
+                                                                                <input required name="attrRequestJsonIdExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestIdJsonExpression">
+                                                                                <div ng-messages="theForm['attrRequestJsonIdExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container ng-if="attributeRequest.requestId == 'requestIdTopicExpression'" flex="60" class="md-block">
+                                                                                <label translate>extension.request-id-topic-expression</label>
+                                                                                <input required name="attrRequestTopicIdExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.requestIdTopicExpression">
+                                                                                <div ng-messages="theForm['attrRequestTopicIdExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.response-topic-expression</label>
+                                                                            <input required name="attributeRequestResponseTopicExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.responseTopicExpression">
+                                                                            <div ng-messages="theForm['attributeRequestResponseTopicExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.value-expression</label>
+                                                                            <input required name="attributeRequestValueExp_{{brokerIndex}}{{attributeRequestIndex}}" ng-model="attributeRequest.valueExpression">
+                                                                            <div ng-messages="theForm['attributeRequestValueExp_' + brokerIndex + attributeRequestIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex layout="row" layout-align="start center">
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click="addAttributeRequest(broker.attributeRequests)" aria-label="{{ 'action.add' | translate }}">
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-attribute-request</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                        <v-accordion id="mqtt-attribute-updates-accordion" class="vAccordion--default">
+                                            <v-pane id="mqtt-attribute-updates-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.attribute-updates' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="broker.attributeUpdates.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item" ng-repeat="(attributeUpdateIndex, attributeUpdate) in broker.attributeUpdates">
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(attributeUpdate, broker.attributeUpdates)">
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.device-name-filter</label>
+                                                                                <input required name="attributeUpdateDeviceNameFilter_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.deviceNameFilter">
+                                                                                <div ng-messages="theForm['attributeUpdateDeviceNameFilter_' + brokerIndex + attributeUpdateIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.attribute-filter</label>
+                                                                                <input required name="attributeUpdateAttributeFilter_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.attributeFilter">
+                                                                                <div ng-messages="theForm['attributeUpdateAttributeFilter_' + brokerIndex + attributeUpdateIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.topic-expression</label>
+                                                                            <input required name="attributeUpdateTopicExp_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.topicExpression">
+                                                                            <div ng-messages="theForm['attributeUpdateTopicExp_' + brokerIndex + attributeUpdateIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.value-expression</label>
+                                                                            <input required name="attributeUpdateValueExp_{{brokerIndex}}{{attributeUpdateIndex}}" ng-model="attributeUpdate.valueExpression">
+                                                                            <div ng-messages="theForm['attributeUpdateValueExp_' + brokerIndex + attributeUpdateIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex layout="row" layout-align="start center">
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click='addAttributeUpdate(broker.attributeUpdates)' aria-label="{{ 'action.add' | translate }}">
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-attribute-update</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                        <v-accordion id="mqtt-server-side-rpc-accordion" class="vAccordion--default">
+                                            <v-pane id="mqtt-server-side-rpc-pane">
+                                                <v-pane-header>
+                                                    {{ 'extension.server-side-rpc' | translate }}
+                                                </v-pane-header>
+                                                <v-pane-content>
+                                                    <div ng-if="broker.serverSideRpc.length > 0">
+                                                        <ol class="list-group">
+                                                            <li class="list-group-item" ng-repeat="(rpcIndex, rpc) in broker.serverSideRpc">
+                                                                <md-button aria-label="{{ 'action.remove' | translate }}" class="md-icon-button" ng-click="removeAttribute(rpc, broker.serverSideRpc)">
+                                                                    <ng-md-icon icon="close" aria-label="{{ 'action.remove' | translate }}"></ng-md-icon>
+                                                                    <md-tooltip md-direction="top">
+                                                                        {{ 'action.remove' | translate }}
+                                                                    </md-tooltip>
+                                                                </md-button>
+                                                                <md-card>
+                                                                    <md-card-content>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.device-name-filter</label>
+                                                                                <input required name="serverSideRpcDeviceNameFilter_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.deviceNameFilter">
+                                                                                <div ng-messages="theForm['serverSideRpcDeviceNameFilter_' + brokerIndex + rpcIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.method-filter</label>
+                                                                                <input required name="serverSideRpcMethodFilter_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.methodFilter">
+                                                                                <div ng-messages="theForm['serverSideRpcMethodFilter_' + brokerIndex + rpcIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.request-topic-expression</label>
+                                                                            <input required name="serverSideRpcRequestTopicExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.requestTopicExpression">
+                                                                            <div ng-messages="theForm['serverSideRpcRequestTopicExp_' + brokerIndex + rpcIndex].$error">
+                                                                                <div translate ng-message="required">extension.field-required</div>
+                                                                            </div>
+                                                                        </md-input-container>
+                                                                        <md-input-container class="md-block">
+                                                                            <label translate>extension.response-topic-expression</label>
+                                                                            <input name="serverSideRpcResponseTopicExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.responseTopicExpression" parse-to-null>
+                                                                        </md-input-container>
+                                                                        <section flex layout="row">
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.response-timeout</label>
+                                                                                <input type="number" name="serverSideRpcResponseTimeout_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.responseTimeout" parse-to-null>
+                                                                            </md-input-container>
+                                                                            <md-input-container flex="50" class="md-block">
+                                                                                <label translate>extension.value-expression</label>
+                                                                                <input required name="serverSideRpcValueExp_{{brokerIndex}}{{rpcIndex}}" ng-model="rpc.valueExpression">
+                                                                                <div ng-messages="theForm['serverSideRpcValueExp_' + brokerIndex + rpcIndex].$error">
+                                                                                    <div translate ng-message="required">extension.field-required</div>
+                                                                                </div>
+                                                                            </md-input-container>
+                                                                        </section>
+                                                                    </md-card-content>
+                                                                </md-card>
+                                                            </li>
+                                                        </ol>
+                                                    </div>
+                                                    <div flex layout="row" layout-align="start center">
+                                                        <md-button class="md-primary md-raised"
+                                                                   ng-click='addServerSideRpc(broker.serverSideRpc)' aria-label="{{ 'action.add' | translate }}">
+                                                            <md-icon class="material-icons">add</md-icon>
+                                                            <span translate>extension.add-server-side-rpc-request</span>
+                                                        </md-button>
+                                                    </div>
+                                                </v-pane-content>
+                                            </v-pane>
+                                        </v-accordion>
+
+                                    </md-card-content>
+                                </md-card>
+                            </li>
+                        </ol>
+                    </div>
+
+                    <div flex layout="row" layout-align="start center">
+                        <md-button class="md-primary md-raised"
+                                   ng-click="addBroker()" aria-label="{{ 'action.add' | translate }}">
+                            <md-icon class="material-icons">add</md-icon>
+                            <span translate>extension.add-broker</span>
+                        </md-button>
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+<!--<pre>
+{{config | json}}
+</pre>-->
+    </md-card-content>
+</md-card>
diff --git a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
index 8c959db..3cc1e1b 100644
--- a/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
+++ b/ui/src/app/extension/extensions-forms/extension-form-opc.tpl.html
@@ -56,16 +56,16 @@
                                                 <label translate>extension.opc-application-name</label>
                                                 <input required name="applicationName_{{serverIndex}}" ng-model="server.applicationName">
                                                 <div ng-messages="theForm['applicationName_' + serverIndex].$error">
-                                                    <div translate ng-message="required">extension.opc-field-required</div>
+                                                    <div translate ng-message="required">extension.field-required</div>
                                                 </div>
                                             </md-input-container>
 
 
-                                            <md-input-container flex="50" class="md-block">
+                                            <md-input-container flex="50" class="md-block" md-is-error="theForm['applicationUri_' + serverIndex].$touched && theForm['applicationUri_' + serverIndex].$invalid">
                                                 <label translate>extension.opc-application-uri</label>
                                                 <input required name="applicationUri_{{serverIndex}}" ng-model="server.applicationUri">
                                                 <div ng-messages="theForm['applicationUri_' + serverIndex].$error">
-                                                    <div translate ng-message="required">extension.opc-field-required</div>
+                                                    <div translate ng-message="required">extension.field-required</div>
                                                 </div>
                                             </md-input-container>
                                         </div>
@@ -73,15 +73,15 @@
 
                                         <div layout="row">
                                             <md-input-container flex="50" class="md-block">
-                                                <label translate>extension.opc-host</label>
+                                                <label translate>extension.host</label>
                                                 <input required name="host_{{serverIndex}}" ng-model="server.host">
                                                 <div ng-messages="theForm['host_' + serverIndex].$error">
-                                                    <div translate ng-message="required">extension.opc-field-required</div>
+                                                    <div translate ng-message="required">extension.field-required</div>
                                                 </div>
                                             </md-input-container>
 
                                             <md-input-container flex="50" class="md-block">
-                                                <label translate>extension.opc-port</label>
+                                                <label translate>extension.port</label>
                                                 <input type="number"
                                                        required
                                                        name="port_{{serverIndex}}"
@@ -92,7 +92,7 @@
                                                 <div ng-messages="theForm['port_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                     <div translate
                                                          ng-message="min"
                                                     >Port should be in a range from 1 to 65535</div>
@@ -113,12 +113,12 @@
                                                 <div ng-messages="theForm['scanPeriodInSeconds_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                 </div>
                                             </md-input-container>
 
                                             <md-input-container flex="50" class="md-block">
-                                                <label translate>extension.opc-timeout-in-millis</label>
+                                                <label translate>extension.timeout</label>
                                                 <input type="number"
                                                        required name="timeoutInMillis_{{serverIndex}}"
                                                        ng-model="server.timeoutInMillis"
@@ -126,7 +126,7 @@
                                                 <div ng-messages="theForm['timeoutInMillis_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                 </div>
                                             </md-input-container>
                                         </div>
@@ -145,7 +145,7 @@
                                                 <div ng-messages="theForm['securityType_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                 </div>
                                             </md-input-container>
 
@@ -157,24 +157,23 @@
                                                 >
                                                     <md-option ng-value="identityType"
                                                                ng-repeat="(identityType, identityValue) in types.extensionIdentityType"
-                                                    ><span ng-bind="::identityValue"></span></md-option>
+                                                    ><span ng-bind="identityValue | translate"></span></md-option>
                                                 </md-select>
                                                 <div ng-messages="theForm['identityType_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                 </div>
                                             </md-input-container>
                                         </div>
 
-
                                         <div ng-if="server.identity.type != 'username'">
                                             <span class=""
                                                   ng-init="server.identity = {'type':'anonymous'}"></span>
                                         </div>
                                         <div layout="row" ng-if="server.identity.type == 'username'">
-                                            <md-input-container flex="50" class="md-block">
-                                                <label translate>extension.opc-username</label>
+                                            <md-input-container flex="50" class="md-block" md-is-error="theForm['identityUsername_' + serverIndex].$touched && theForm['identityUsername_' + serverIndex].$invalid">
+                                                <label translate>extension.username</label>
                                                 <input required
                                                        name="identityUsername_{{serverIndex}}"
                                                        ng-model="server.identity.username"
@@ -182,25 +181,24 @@
                                                 <div ng-messages="theForm['identityUsername_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                 </div>
                                             </md-input-container>
 
-                                            <md-input-container flex="50" class="md-block">
-                                                <label translate>extension.opc-password</label>
+                                            <md-input-container flex="50" class="md-block" md-is-error="theForm['identityPassword_' + serverIndex].$touched && theForm['identityPassword_' + serverIndex].$invalid">
+                                                <label translate>extension.password</label>
                                                 <input required
                                                        name="identityPassword_{{serverIndex}}" ng-model="server.identity.password">
                                                 <div ng-messages="theForm['identityPassword_' + serverIndex].$error">
                                                     <div translate
                                                          ng-message="required"
-                                                    >extension.opc-field-required</div>
+                                                    >extension.field-required</div>
                                                 </div>
                                             </md-input-container>
                                         </div>
 
-
                                         <v-accordion id="opc-attributes-accordion" class="vAccordion--default">
-                                            <v-pane id="opc-attributes-pane">
+                                            <v-pane id="opc-attributes-pane" expanded="true">
                                                 <v-pane-header>
                                                     {{ 'extension.opc-keystore' | translate }}
                                                 </v-pane-header>
@@ -212,38 +210,40 @@
                                                             <md-option ng-value="keystoreType" ng-repeat="(keystoreType, keystoreValue) in types.extensionKeystoreType"><span ng-bind="::keystoreValue"></span></md-option>
                                                         </md-select>
                                                         <div ng-messages="theForm['keystoreType_'+serverIndex].$error">
-                                                            <div translate ng-message="required">extension.opc-field-required</div>
+                                                            <div translate ng-message="required">extension.field-required</div>
                                                         </div>
                                                     </md-input-container>
 
-                                                    <div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}">
-                                                        <span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
-                                                        <label class="tb-label" translate>extension.opc-keystore-location</label>
-                                                        <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
-                                                            <div class="tb-file-clear-container">
-                                                                <md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
-                                                                    <md-tooltip md-direction="top">
-                                                                        {{ 'action.remove' | translate }}
-                                                                    </md-tooltip>
-                                                                    <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
-                                                                </md-button>
-                                                            </div>
-                                                            <div class="alert tb-flow-drop" flow-drop>
-                                                                <label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
-                                                                <input flow-attrs="{accept:'.pfx,.p12'}"
-                                                                       type="file"
-                                                                       class="file-input"
-                                                                       flow-btn id="dropFileKeystore_{{serverIndex}}"
-                                                                       name="keystoreFile"
-                                                                       ng-model="server.keystore.file"
-                                                                >
+                                                    <section class="dropdown-section">
+                                                        <div class="tb-container" ng-class="{'ng-invalid':!server.keystore.file}">
+                                                            <span ng-init='fieldsToFill = {"fileName":"fileName", "file":"file"}'></span>
+                                                            <label class="tb-label" translate>extension.opc-keystore-location</label>
+                                                            <div flow-init="{singleFile:true}" flow-file-added='fileAdded($file, server.keystore, fieldsToFill)' class="tb-file-select-container">
+                                                                <div class="tb-file-clear-container">
+                                                                    <md-button ng-click='clearFile(server.keystore, fieldsToFill)' class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
+                                                                        <md-tooltip md-direction="top">
+                                                                            {{ 'action.remove' | translate }}
+                                                                        </md-tooltip>
+                                                                        <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">close</md-icon>
+                                                                    </md-button>
+                                                                </div>
+                                                                <div class="alert tb-flow-drop" flow-drop>
+                                                                    <label for="dropFileKeystore_{{serverIndex}}" translate>extension.drop-file</label>
+                                                                    <input flow-attrs="{accept:'.pfx,.p12'}"
+                                                                           type="file"
+                                                                           class="file-input"
+                                                                           flow-btn id="dropFileKeystore_{{serverIndex}}"
+                                                                           name="keystoreFile"
+                                                                           ng-model="server.keystore.file"
+                                                                    >
+                                                                </div>
                                                             </div>
                                                         </div>
-                                                    </div>
-                                                    <div>
-                                                        <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
-                                                        <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
-                                                    </div>
+                                                        <div class="dropdown-messages">
+                                                            <div ng-if="!server.keystore[fieldsToFill.fileName]" class="tb-error-message" translate>extension.no-file</div>
+                                                            <div ng-if="server.keystore[fieldsToFill.fileName]">{{server.keystore[fieldsToFill.fileName]}}</div>
+                                                        </div>
+                                                    </section>
 
 
                                                     <div flex layout="row">
@@ -251,7 +251,7 @@
                                                             <label translate>extension.opc-keystore-password</label>
                                                             <input required name="keystorePassword_{{serverIndex}}" ng-model="server.keystore.password">
                                                             <div ng-messages="theForm['keystorePassword_' + serverIndex].$error">
-                                                                <div translate ng-message="required">extension.opc-field-required</div>
+                                                                <div translate ng-message="required">extension.field-required</div>
                                                             </div>
                                                         </md-input-container>
 
@@ -259,7 +259,7 @@
                                                             <label translate>extension.opc-keystore-alias</label>
                                                             <input required name="keystoreAlias_{{serverIndex}}" ng-model="server.keystore.alias">
                                                             <div ng-messages="theForm['keystoreAlias_' + serverIndex].$error">
-                                                                <div translate ng-message="required">extension.opc-field-required</div>
+                                                                <div translate ng-message="required">extension.field-required</div>
                                                             </div>
                                                         </md-input-container>
                                                     </div>
@@ -268,7 +268,7 @@
                                                         <label translate>extension.opc-keystore-key-password</label>
                                                         <input required name="keystoreKeyPassword_{{serverIndex}}" ng-model="server.keystore.keyPassword">
                                                         <div ng-messages="theForm['keystoreKeyPassword_' + serverIndex].$error">
-                                                            <div translate ng-message="required">extension.opc-field-required</div>
+                                                            <div translate ng-message="required">extension.field-required</div>
                                                         </div>
                                                     </md-input-container>
 
@@ -282,7 +282,7 @@
                                         >
                                             <v-pane id="opc-attributes-pane">
                                                 <v-pane-header>
-                                                    {{ 'extension.opc-mapping' | translate }}
+                                                    {{ 'extension.mapping' | translate }}
                                                 </v-pane-header>
                                                 <v-pane-content>
                                                     <div ng-if="server.mapping.length > 0">
@@ -312,7 +312,7 @@
                                                                                 <div ng-messages="theForm['deviceNodePattern_' + serverIndex + mapIndex].$error">
                                                                                     <div translate
                                                                                          ng-message="required"
-                                                                                    >extension.opc-field-required</div>
+                                                                                    >extension.field-required</div>
                                                                                 </div>
                                                                             </md-input-container>
 
@@ -325,7 +325,7 @@
                                                                                 <div ng-messages="theForm['deviceNamePattern_' + serverIndex + mapIndex].$error">
                                                                                     <div translate
                                                                                          ng-message="required"
-                                                                                    >extension.opc-field-required</div>
+                                                                                    >extension.field-required</div>
                                                                                 </div>
                                                                             </md-input-container>
                                                                         </div>
@@ -336,7 +336,7 @@
                                                                         >
                                                                             <v-pane id="opc-attributes-pane">
                                                                                 <v-pane-header>
-                                                                                    {{ 'extension.opc-mapping-attributes' | translate }}
+                                                                                    {{ 'extension.attributes' | translate }}
                                                                                 </v-pane-header>
                                                                                 <v-pane-content>
                                                                                     <div ng-show="map.attributes.length > 0">
@@ -369,7 +369,7 @@
                                                                                                                 <div ng-messages="theForm['opcAttributeKey_' + serverIndex + mapIndex + attributeIndex].$error">
                                                                                                                     <div translate
                                                                                                                          ng-message="required"
-                                                                                                                    >extension.opc-field-required</div>
+                                                                                                                    >extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
                                                                                                             <md-input-container flex="40" class="md-block tb-container-for-select">
@@ -400,7 +400,7 @@
                                                                                                                 <div ng-messages="theForm['opcAttributeValue_' + serverIndex + mapIndex + attributeIndex].$error">
                                                                                                                     <div translate
                                                                                                                          ng-message="required"
-                                                                                                                    >extension.opc-field-required</div>
+                                                                                                                    >extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
 
@@ -417,11 +417,8 @@
                                                                                                    ng-click="addNewAttribute(map.attributes)"
                                                                                                    aria-label="{{ 'action.add' | translate }}"
                                                                                         >
-                                                                                            <md-tooltip md-direction="top">
-                                                                                                {{ 'extension.add-map' | translate }}
-                                                                                            </md-tooltip>
                                                                                             <md-icon class="material-icons">add</md-icon>
-                                                                                            <span translate>action.add</span>
+                                                                                            <span translate>add-attribute</span>
                                                                                         </md-button>
                                                                                     </div>
                                                                                 </v-pane-content>
@@ -431,7 +428,7 @@
                                                                         <v-accordion id="opc-timeseries-accordion" class="vAccordion--default">
                                                                             <v-pane id="opc-timeseries-pane">
                                                                                 <v-pane-header>
-                                                                                    {{ 'extension.opc-timeseries' | translate }}
+                                                                                    {{ 'extension.timeseries' | translate }}
                                                                                 </v-pane-header>
                                                                                 <v-pane-content>
                                                                                     <div ng-show="map.timeseries.length > 0">
@@ -460,7 +457,7 @@
                                                                                                                 <div ng-messages="theForm['opcTimeseriesKey_' + serverIndex + mapIndex + timeseriesIndex].$error">
                                                                                                                     <div translate
                                                                                                                          ng-message="required"
-                                                                                                                    >extension.opc-field-required</div>
+                                                                                                                    >extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
                                                                                                             <md-input-container flex="40"
@@ -480,7 +477,7 @@
                                                                                                                 <div ng-messages="theForm['opcTimeseriesType_' + serverIndex + mapIndex + timeseriesIndex].$error">
                                                                                                                     <div translate
                                                                                                                          ng-message="required"
-                                                                                                                    >extension.opc-field-required</div>
+                                                                                                                    >extension.field-required</div>
                                                                                                                 </div>
                                                                                                             </md-input-container>
                                                                                                         </section>
@@ -503,11 +500,8 @@
                                                                                                    ng-click="addNewAttribute(map.timeseries)"
                                                                                                    aria-label="{{ 'action.add' | translate }}"
                                                                                         >
-                                                                                            <md-tooltip md-direction="top">
-                                                                                                {{ 'extension.add-timeseries' | translate }}
-                                                                                            </md-tooltip>
                                                                                             <md-icon class="material-icons">add</md-icon>
-                                                                                            <span translate>action.add</span>
+                                                                                            <span translate>extension.add-timeseries</span>
                                                                                         </md-button>
                                                                                     </div>
                                                                                 </v-pane-content>
@@ -528,11 +522,8 @@
                                                                    ng-click="addMap(server.mapping)"
                                                                    aria-label="{{ 'action.add' | translate }}"
                                                         >
-                                                            <md-tooltip md-direction="top">
-                                                                {{ 'extension.add-map' | translate }}
-                                                            </md-tooltip>
                                                             <md-icon class="material-icons">add</md-icon>
-                                                            <span translate>action.add</span>
+                                                            <span translate>extension.add-map</span>
                                                         </md-button>
                                                     </div>
                                                 </v-pane-content>
@@ -553,7 +544,7 @@
                                        aria-label="{{ 'action.add' | translate }}"
                             >
                                 <md-icon class="material-icons">add</md-icon>
-                                <span translate>extension.opc-add-another-server</span>
+                                <span translate>extension.opc-add-server</span>
                             </md-button>
                         </div>
 
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js
index e109ad9..03dd58e 100644
--- a/ui/src/app/extension/extension-table.directive.js
+++ b/ui/src/app/extension/extension-table.directive.js
@@ -43,7 +43,7 @@ export default function ExtensionTableDirective() {
 }
 
 /*@ngInject*/
-function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService) {
+function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService) {
 
     let vm = this;
 
@@ -83,51 +83,6 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
         }
     });
 
-    $scope.$watch('vm.transferredAttributes', function () {
-        if (vm.transferredAttributes && vm.transferredAttributes.data && vm.transferredAttributes.data.length) {
-            vm.transferredAttributes.data
-                .some(attribute=>{
-                    if (attribute.key === "appliedConfiguration") {
-                        vm.appliedConfiguration = attribute.value;
-                    }
-                });
-
-            checkForSync();
-        }
-    });
-
-
-    checkForSync();
-    function checkForSync() {
-        if (vm.appliedConfiguration === vm.extensionsJSON) {
-            vm.syncStatus = $translate.instant('extension.sync.sync');
-            vm.syncLastTime = formatDate();
-        } else {
-            vm.syncStatus = $translate.instant('extension.sync.not-sync');
-        }
-    }
-
-
-    function formatDate(date) {
-        let d;
-        if (date) {
-            d = date;
-        } else {
-            d = new Date();
-        }
-
-        d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds());
-        return d;
-
-
-        function addZero(num) {
-            if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) {
-                num = '0' + num;
-            }
-            return num;
-        }
-    }
-
     function enterFilterMode() {
         vm.query.search = '';
     }
@@ -288,57 +243,73 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
     }
 
 
-    // vm.subscriptionId = null;
-    // $scope.checkSubscription = function() {
-    //     var newSubscriptionId = null;
-    //     if (vm.entityId && vm.entityType) {
-    //         newSubscriptionId = attributeService.subscribeForEntityAttributes(vm.entityType, vm.entityId, 'extension/SHARED_SCOPE');
-    //     }
-    //     if (vm.subscriptionId && vm.subscriptionId != newSubscriptionId) {
-    //         attributeService.unsubscribeForEntityAttributes(vm.subscriptionId);
-    //     }
-    //     vm.subscriptionId = newSubscriptionId;
-    // }
-    //
-    //
-    // // $scope.attributesData = {};
-    // // var entityAttributesSubscriptionMap = [];
-    // //
-    // $scope.subscribeForEntityAttributes = function (entityType=vm.entityType, entityId=vm.entityId, attributeScope="SHARED_SCOPE") {
-    //     var subscriptionId = entityType + entityId + attributeScope;
-    //     var entityAttributesSubscription = entityAttributesSubscriptionMap[subscriptionId];
-    //     if (!entityAttributesSubscription) {
-    //         var subscriptionCommand = {
-    //             entityType: entityType,
-    //             entityId: entityId,
-    //             scope: attributeScope
-    //         };
-    //
-    //         var type = attributeScope === types.latestTelemetry.value ?
-    //             types.dataKeyType.timeseries : types.dataKeyType.attribute;
-    //
-    //         var subscriber = {
-    //             subscriptionCommands: [subscriptionCommand],
-    //             type: type,
-    //             onData: function (data) {
-    //                 if (data.data) {
-    //                     onSubscriptionData(data.data, subscriptionId);
-    //                 }
-    //             }
-    //         };
-    //         entityAttributesSubscription = {
-    //             subscriber: subscriber,
-    //             attributes: null
-    //         };
-    //         entityAttributesSubscriptionMap[subscriptionId] = entityAttributesSubscription;
-    //         telemetryWebsocketService.subscribe(subscriber);
-    //     }
-    //     return subscriptionId;
-    // };
-    //
-    // function onSubscriptionData(data/*, subscriptionId*/) {
-    //     $scope.attributesData = data;
-    // }
-
-    // telemetryWebsocketService.subscribe(subscriber);
+
+    if (vm.entityId && vm.entityType) {
+        $scope.subscriber = {
+            subscriptionCommands: [{
+                entityType: vm.entityType,
+                entityId: vm.entityId,
+                scope: 'CLIENT_SCOPE'
+            }],
+            type: 'attribute',
+            onData: function (data) {
+                if (data.data) {
+                    onSubscriptionData(data.data/*, subscriptionId*/);
+                }
+            }
+        };
+    }
+    telemetryWebsocketService.subscribe($scope.subscriber);
+
+
+    $scope.$on('$destroy', function() {
+        telemetryWebsocketService.unsubscribe($scope.subscriber);
+    });
+
+
+
+
+    function onSubscriptionData(data/*, subscriptionId*/) {
+        //$scope.attributesData = data.;
+
+        if (data.appliedConfiguration && data.appliedConfiguration[0] && data.appliedConfiguration[0][1]) {
+            vm.appliedConfiguration = data.appliedConfiguration[0][1];
+            checkForSync();
+            $scope.$digest();
+        }
+    }
+
+
+    checkForSync();
+    function checkForSync() {
+        if (vm.appliedConfiguration === vm.extensionsJSON) {
+            vm.syncStatus = $translate.instant('extension.sync.sync');
+            vm.syncLastTime = formatDate();
+        } else {
+            vm.syncStatus = $translate.instant('extension.sync.not-sync');
+        }
+    }
+
+    function formatDate(date) {
+        let d;
+        if (date) {
+            d = date;
+        } else {
+            d = new Date();
+        }
+
+        d = d.getFullYear() +'/'+ addZero(d.getMonth()+1) +'/'+ addZero(d.getDate()) + ' ' + addZero(d.getHours()) + ':' + addZero(d.getMinutes()) +':'+ addZero(d.getSeconds());
+        return d;
+
+
+        function addZero(num) {
+            if ((angular.isNumber(num) && num < 10) || (angular.isString(num) && num.length === 1)) {
+                num = '0' + num;
+            }
+            return num;
+        }
+    }
+
+
+    //telemetryWebsocketService.subscribe(subscriber);
 }
\ No newline at end of file
diff --git a/ui/src/app/extension/index.js b/ui/src/app/extension/index.js
index 4c9f812..acb97be 100644
--- a/ui/src/app/extension/index.js
+++ b/ui/src/app/extension/index.js
@@ -16,12 +16,14 @@
 
 import ExtensionTableDirective from './extension-table.directive';
 import ExtensionFormHttpDirective from './extensions-forms/extension-form-http.directive';
+import ExtensionFormMqttDirective from './extensions-forms/extension-form-mqtt.directive'
 import ExtensionFormOpcDirective from './extensions-forms/extension-form-opc.directive';
 import {ParseToNull} from './extension-dialog.controller';
 
 export default angular.module('thingsboard.extension', [])
     .directive('tbExtensionTable', ExtensionTableDirective)
     .directive('tbExtensionFormHttp', ExtensionFormHttpDirective)
+    .directive('tbExtensionFormMqtt', ExtensionFormMqttDirective)
     .directive('tbExtensionFormOpc', ExtensionFormOpcDirective)
     .directive('parseToNull', ParseToNull)
     .name;
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 8272fbb..6ee16ca 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -738,13 +738,8 @@ export default angular.module('thingsboard.locale', [])
                     "id": "Id",
                     "extension-id": "Extension id",
                     "extension-type": "Extension type",
-                    "transformer-json": "JSON*",
-                    "id-required": "Extension id is required.",
+                    "transformer-json": "JSON *",
                     "unique-id-required": "Current extension id already exists.",
-                    "type-required": "Extension type is required.",
-                    "required-type": "Type is required.",
-                    "required-key": "Key is required.",
-                    "required-value": "Value is required.",
                     "delete": "Delete extension",
                     "add": "Add extension",
                     "edit": "Edit extension",
@@ -754,18 +749,13 @@ export default angular.module('thingsboard.locale', [])
                     "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.",
                     "converters": "Converters",
                     "converter-id": "Converter id",
-                    "converter-id-required": "Converter id is required.",
                     "configuration": "Configuration",
                     "converter-configurations": "Converter configurations",
                     "token": "Security token",
                     "add-converter": "Add converter",
-                    "add-converter-prompt": "Please add converter",
                     "add-config": "Add converter configuration",
-                    "add-config-prompt": "Please add converter configuration",
                     "device-name-expression": "Device name expression",
-                    "device-name-expression-required": "Device name expression is required.",
                     "device-type-expression": "Device type expression",
-                    "device-type-expression-required": "Device type expression is required.",
                     "custom": "Custom",
                     "to-double": "To Double",
                     "transformer": "Transformer",
@@ -777,7 +767,6 @@ export default angular.module('thingsboard.locale', [])
                     "timeseries": "Timeseries",
                     "add-timeseries": "Add timeseries",
 
-
                     "sync": {
                         "status": "Status",
                         "sync": "Sync",
@@ -785,18 +774,69 @@ export default angular.module('thingsboard.locale', [])
                         "last-sync-time": "Last sync time",
                     },
 
-                    "opc-field-required": "Field is required",
+
+                    "field-required": "Field is required",
+                    "brokers": "Brokers",
+                    "add-broker": "Add broker",
+                    "host": "Host",
+                    "port": "Port",
+                    "port-range": "Port should be in a range from 1 to 65535.",
+                    "ssl": "Ssl",
+                    "credentials": "Credentials",
+                    "username": "Username",
+                    "password": "Password",
+                    "retry-interval": "Retry interval in milliseconds",
+                    "anonymous": "Anonymous",
+                    "basic": "Basic",
+                    "pem": "PEM",
+                    "ca-cert": "CA certificate file *",
+                    "private-key": "Private key file *",
+                    "cert": "Certificate file *",
+                    "no-file": "No file selected.",
+                    "drop-file": "Drop a file or click to select a file to upload.",
+                    "mapping": "Mapping",
+                    "topic-filter": "Topic filter",
+                    "converter-type": "Converter type",
+                    "converter-json": "Json",
+                    "json-name-expression": "Device name json expression",
+                    "topic-name-expression": "Device name topic expression",
+                    "json-type-expression": "Device type json expression",
+                    "topic-type-expression": "Device type topic expression",
+                    "attribute-key-expression": "Attribute key expression",
+                    "attr-json-key-expression": "Attribute key json expression",
+                    "attr-topic-key-expression": "Attribute key topic expression",
+                    "request-id-expression": "Request id expression",
+                    "request-id-json-expression": "Request id json expression",
+                    "request-id-topic-expression": "Request id topic expression",
+                    "response-topic-expression": "Response topic expression",
+                    "value-expression": "Value expression",
+                    "topic": "Topic",
+                    "timeout": "Timeout in milliseconds",
+                    "converter-json-required": "Converter json is required.",
+                    "converter-json-parse": "Unable to parse converter json.",
+                    "filter-expression": "Filter expression",
+                    "connect-requests": "Connect requests",
+                    "add-connect-request": "Add connect request",
+                    "disconnect-requests": "Disconnect requests",
+                    "add-disconnect-request": "Add disconnect request",
+                    "attribute-requests": "Attribute requests",
+                    "add-attribute-request": "Add attribute request",
+                    "attribute-updates": "Attribute updates",
+                    "add-attribute-update": "Add attribute update",
+                    "server-side-rpc": "Server side RPC",
+                    "add-server-side-rpc-request": "Add server-side RPC request",
+                    "device-name-filter": "Device name filter",
+                    "attribute-filter": "Attribute filter",
+                    "method-filter": "Method filter",
+                    "request-topic-expression": "Request topic expression",
+                    "response-timeout": "Response timeout in milliseconds",
+                    "topic-expression": "Topic expression",
+                    "client-scope": "Client scope",
                     "opc-server": "Servers",
-                    "opc-add-server-hint": "Add server",
-                    "opc-add-server-prompt": "Please add server",
-                    "opc-server-id": "Server id",
-                    "opc-timeseries": "Timeseries",
+                    "opc-add-server": "Add server",
                     "opc-application-name": "Application name",
                     "opc-application-uri": "Application uri",
-                    "opc-host": "Host",
-                    "opc-port": "Port",
                     "opc-scan-period-in-seconds": "Scan period in seconds",
-                    "opc-timeout-in-millis": "Timeout in milliseconds",
                     "opc-security": "Security",
                     "opc-identity": "Identity",
                     "opc-keystore": "Keystore",
@@ -806,19 +846,14 @@ export default angular.module('thingsboard.locale', [])
                     "opc-keystore-password":"Password",
                     "opc-keystore-alias":"Alias",
                     "opc-keystore-key-password":"Key password",
-                    "opc-mapping":"Mapping",
                     "opc-device-node-pattern":"Device node pattern",
                     "opc-device-name-pattern":"Device name pattern",
-                    "opc-mapping-attributes":"Mapping attributes",
-                    "opc-username":"Username",
-                    "opc-password":"Password",
-                    "opc-add-another-server":"Add another server",
                 },
                 "fullscreen": {
                     "expand": "Expand to fullscreen",
                     "exit": "Exit fullscreen",
                     "toggle": "Toggle fullscreen mode",
-                    "fullscreen": "Fullscreen",
+                    "fullscreen": "Fullscreen"
                 },
                 "function": {
                     "function": "Function"
@@ -936,7 +971,6 @@ export default angular.module('thingsboard.locale', [])
                     "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.",
                     "copyId": "Copy plugin Id",
                     "idCopiedMessage": "Plugin Id has been copied to clipboard"
-
                 },
                 "position": {
                     "top": "Top",