thingsboard-aplcache

Details

diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html
index bc66a79..1ec0134 100644
--- a/ui/src/app/device/devices.tpl.html
+++ b/ui/src/app/device/devices.tpl.html
@@ -70,6 +70,7 @@
         <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}">
             <tb-extension-table flex
                                 entity-id="vm.grid.operatingItem().id.id"
+                                entity-name="vm.grid.operatingItem().name"
                                 entity-type="{{vm.types.entityType.device}}">
             </tb-extension-table>
         </md-tab>
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js
index 7fe7139..5e5b036 100644
--- a/ui/src/app/extension/extension-table.directive.js
+++ b/ui/src/app/extension/extension-table.directive.js
@@ -36,7 +36,8 @@ export default function ExtensionTableDirective() {
             entityId: '=',
             entityType: '@',
             inWidget: '@?',
-            ctx: '=?'
+            ctx: '=?',
+            entityName: '='
         },
         controller: ExtensionTableController,
         controllerAs: 'vm',
@@ -45,7 +46,7 @@ export default function ExtensionTableDirective() {
 }
 
 /*@ngInject*/
-function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService) {
+function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService, importExport) {
 
     let vm = this;
 
@@ -122,6 +123,16 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
             addExtension();
         }
     });
+    $scope.$on("exportExtensions", function($event, source) {
+        if(source.entityId == vm.entityId) {
+            vm.exportExtensions(source.entityName);
+        }
+    });
+    $scope.$on("importExtensions", function($event, source) {
+        if(source.entityId == vm.entityId) {
+            vm.importExtensions();
+        }
+    });
 
     function enterFilterMode() {
         vm.query.search = '';
@@ -364,4 +375,23 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
             return num;
         }
     }
+
+    vm.importExtensions = function($event) {
+        importExport.importExtension($event, {"entityType":vm.entityType, "entityId":vm.entityId, "successFunc":reloadExtensions});
+    };
+    vm.exportExtensions = function(widgetSourceEntityName) {
+        if(vm.inWidget) {
+            importExport.exportToPc(vm.extensionsJSON,  widgetSourceEntityName + '_configuration.json');
+        } else {
+            importExport.exportToPc(vm.extensionsJSON,  vm.entityName + '_configuration.json');
+        }
+    };
+
+    /*change function for widget implementing, like vm.exportExtensions*/
+    vm.exportExtension = function($event, extension) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        importExport.exportToPc(extension,  vm.entityName +'_'+ extension.id +'_configuration.json');
+    };
 }
\ No newline at end of file
diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss
index ac57d4a..7c0a0d8 100644
--- a/ui/src/app/extension/extension-table.scss
+++ b/ui/src/app/extension/extension-table.scss
@@ -16,11 +16,16 @@
 @import '../../scss/constants';
 
 
-.extension-table md-input-container .md-errors-spacer {
-  min-height: 0;
-}
-
 .extension-table {
+
+  md-input-container .md-errors-spacer {
+    min-height: 0;
+  }
+
+  /*&.tb-data-table table.md-table tbody tr td.tb-action-cell,
+  &.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell {
+    width: 114px;
+  }*/
   .sync-widget {
     max-height: 90px;
     overflow: hidden;
@@ -31,7 +36,6 @@
   }
 }
 
-
 .extension__syncStatus--black {
   color: #000000!important;
 }
diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html
index e155e9e..b86061c 100644
--- a/ui/src/app/extension/extension-table.tpl.html
+++ b/ui/src/app/extension/extension-table.tpl.html
@@ -23,6 +23,19 @@
             <div class="md-toolbar-tools">
                 <span translate>{{ 'extension.extensions' }}</span>
                 <span flex></span>
+
+                <md-button class="md-icon-button" ng-click="vm.importExtensions($event)">
+                    <md-icon>file_upload</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'extension.import-extensions-configuration' | translate }}
+                    </md-tooltip>
+                </md-button>
+                <md-button class="md-icon-button" ng-click="vm.exportExtensions()">
+                    <md-icon>file_download</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'extension.export-extensions-configuration' | translate }}
+                    </md-tooltip>
+                </md-button>
                 <md-button class="md-icon-button" ng-click="vm.addExtension($event)">
                     <md-icon>add</md-icon>
                     <md-tooltip md-direction="top">
@@ -111,6 +124,14 @@
                     <td md-cell>{{ extension.id }}</td>
                     <td md-cell>{{ extension.type }}</td>
                     <td md-cell class="tb-action-cell">
+
+                        <!--<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.exportExtension($event, extension)">
+                            <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">file_download</md-icon>
+                            <md-tooltip md-direction="top">
+                                {{ 'extension.export-extension' | translate }}
+                            </md-tooltip>
+                        </md-button>-->
+
                         <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)">
                             <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
                             <md-tooltip md-direction="top">
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index 86d5240..62e1bb3 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -24,8 +24,9 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html';
 /* eslint-disable no-undef, angular/window-service, angular/document-service */
 
 /*@ngInject*/
-export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, utils, types, dashboardUtils,
-                                     entityService, dashboardService, pluginService, ruleService, widgetService, toast) {
+export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types,
+                                     dashboardUtils, entityService, dashboardService, pluginService, ruleService,
+                                     widgetService, toast, attributeService) {
 
 
     var service = {
@@ -40,8 +41,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         exportWidgetType: exportWidgetType,
         importWidgetType: importWidgetType,
         exportWidgetsBundle: exportWidgetsBundle,
-        importWidgetsBundle: importWidgetsBundle
-    }
+        importWidgetsBundle: importWidgetsBundle,
+        exportExtension: exportExtension,
+        importExtension: importExtension,
+        exportToPc: exportToPc
+    };
 
     return service;
 
@@ -614,6 +618,84 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         return true;
     }
 
+
+
+    function exportExtension(extensionId) {
+
+        getExtension(extensionId)
+            .then(
+                function success(extension) {
+                    var name = extension.title;
+                    name = name.toLowerCase().replace(/\W/g,"_");
+                    exportToPc(prepareExport(extension), name + '.json');
+                },
+                function fail(rejection) {
+                    var message = rejection;
+                    if (!message) {
+                        message = $translate.instant('error.unknown-error');
+                    }
+                    toast.showError($translate.instant('extension.export-failed-error', {error: message}));
+                }
+            );
+
+        function getExtension(extensionId) {
+            var deferred = $q.defer();
+            var url = '/api/plugins/telemetry/DEVICE/' + extensionId;
+            $http.get(url, null)
+                .then(function success(response) {
+                    deferred.resolve(response.data);
+                }, function fail() {
+                    deferred.reject();
+                });
+            return deferred.promise;
+        }
+
+    }
+
+    function importExtension($event, options) {
+        var deferred = $q.defer();
+        openImportDialog($event, 'extension.import-extensions', 'extension.file')
+            .then(
+                function success(extension) {
+                    if (!validateImportedExtension(extension)) {
+                        toast.showError($translate.instant('extension.invalid-file-error'));
+                        deferred.reject();
+                    } else {
+                        attributeService
+                            .saveEntityAttributes(
+                                options.entityType,
+                                options.entityId,
+                                types.attributesScope.shared.value,
+                                [{
+                                    key: "configuration",
+                                    value: angular.toJson(extension)
+                                }]
+                            )
+                            .then(function success() {
+                                options.successFunc();
+                            });
+                    }
+                },
+                function fail() {
+                    deferred.reject();
+                }
+            );
+        return deferred.promise;
+    }
+
+    function validateImportedExtension(configuration) {
+        if (configuration.length) {
+            for (let i = 0; i < configuration.length; i++) {
+                if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id )|| angular.isUndefined(configuration[i].type)) {
+                    return false;
+                }
+            }
+        } else {
+            return false;
+        }
+        return true;
+    }
+
     function processEntityAliases(entityAliases, aliasIds) {
         var deferred = $q.defer();
         var missingEntityAliases = {};
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 089a0f5..ab672de 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -847,6 +847,14 @@ export default angular.module('thingsboard.locale', [])
                         "last-sync-time": "Last sync time",
                         "not-available": "Not available"
                     },
+
+                    "export-extensions-configuration":"Export extensions configuration",
+                    "import-extensions-configuration":"Import extensions configuration",
+                    "import-extensions": "Import extensions",
+                    "import-extension": "Import extension",
+                    "export-extension": "Export extension",
+                    "file": "Extensions file",
+                    "invalid-file-error": "Invalid extension file"
                 },
                 "fullscreen": {
                     "expand": "Expand to fullscreen",
diff --git a/ui/src/app/widget/lib/extensions-table-widget.js b/ui/src/app/widget/lib/extensions-table-widget.js
index ded52ff..3e5ac61 100644
--- a/ui/src/app/widget/lib/extensions-table-widget.js
+++ b/ui/src/app/widget/lib/extensions-table-widget.js
@@ -66,7 +66,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) {
         }
         vm.ctx.widgetTitle = vm.extensionsTitle;
 
-        vm.ctx.widgetActions = [vm.addAction, vm.searchAction, vm.refreshAction];
+        vm.ctx.widgetActions = [vm.importExtensionsAction, vm.exportExtensionsAction, vm.addAction, vm.searchAction, vm.refreshAction];
     }
 
     function updateDatasources() {
@@ -78,7 +78,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) {
 
     vm.changeSelectedSource = function(source) {
         vm.selectedSource = source;
-    }
+    };
 
     vm.searchAction = {
         name: "action.search",
@@ -96,7 +96,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) {
             $scope.$broadcast("refreshExtensions", vm.selectedSource);
         },
         icon: "refresh"
-    }
+    };
 
     vm.addAction = {
         name: "action.add",
@@ -105,7 +105,25 @@ function ExtensionsTableWidgetController($scope, $translate, utils) {
             $scope.$broadcast("addExtension", vm.selectedSource);
         },
         icon: "add"
-    }
+    };
+
+    vm.exportExtensionsAction = {
+        name: "extension.export-extensions-configuration",
+        show: true,
+        onAction: function() {
+            $scope.$broadcast("exportExtensions", vm.selectedSource);
+        },
+        icon: "file_download"
+    };
+
+    vm.importExtensionsAction = {
+        name: "extension.import-extensions-configuration",
+        show: true,
+        onAction: function() {
+            $scope.$broadcast("importExtensions", vm.selectedSource);
+        },
+        icon: "file_upload"
+    };
 
     $scope.$on("filterMode", function($event, mode) {
         vm.tabsHidden = mode;