diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html index bc66a79b8b..1ec0134444 100644 --- a/ui/src/app/device/devices.tpl.html +++ b/ui/src/app/device/devices.tpl.html @@ -70,6 +70,7 @@ diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js index 7fe7139275..5e5b036a13 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 ac57d4a780..7c0a0d8578 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 e155e9e132..b86061cdce 100644 --- a/ui/src/app/extension/extension-table.tpl.html +++ b/ui/src/app/extension/extension-table.tpl.html @@ -23,6 +23,19 @@
{{ 'extension.extensions' }} + + + file_upload + + {{ 'extension.import-extensions-configuration' | translate }} + + + + file_download + + {{ 'extension.export-extensions-configuration' | translate }} + + add @@ -111,6 +124,14 @@ {{ extension.id }} {{ extension.type }} + + + edit diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 86d5240953..62e1bb3e2d 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 089a0f5c51..ab672de30d 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 ded52ff590..3e5ac61741 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;