Showing
7 changed files
with
179 additions
and
15 deletions
@@ -70,6 +70,7 @@ | @@ -70,6 +70,7 @@ | ||
70 | <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}"> | 70 | <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.operatingItem().additionalInfo.gateway" md-on-select="vm.grid.triggerResize()" label="{{ 'extension.extensions' | translate }}"> |
71 | <tb-extension-table flex | 71 | <tb-extension-table flex |
72 | entity-id="vm.grid.operatingItem().id.id" | 72 | entity-id="vm.grid.operatingItem().id.id" |
73 | + entity-name="vm.grid.operatingItem().name" | ||
73 | entity-type="{{vm.types.entityType.device}}"> | 74 | entity-type="{{vm.types.entityType.device}}"> |
74 | </tb-extension-table> | 75 | </tb-extension-table> |
75 | </md-tab> | 76 | </md-tab> |
@@ -36,7 +36,8 @@ export default function ExtensionTableDirective() { | @@ -36,7 +36,8 @@ export default function ExtensionTableDirective() { | ||
36 | entityId: '=', | 36 | entityId: '=', |
37 | entityType: '@', | 37 | entityType: '@', |
38 | inWidget: '@?', | 38 | inWidget: '@?', |
39 | - ctx: '=?' | 39 | + ctx: '=?', |
40 | + entityName: '=' | ||
40 | }, | 41 | }, |
41 | controller: ExtensionTableController, | 42 | controller: ExtensionTableController, |
42 | controllerAs: 'vm', | 43 | controllerAs: 'vm', |
@@ -45,7 +46,7 @@ export default function ExtensionTableDirective() { | @@ -45,7 +46,7 @@ export default function ExtensionTableDirective() { | ||
45 | } | 46 | } |
46 | 47 | ||
47 | /*@ngInject*/ | 48 | /*@ngInject*/ |
48 | -function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService) { | 49 | +function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService, importExport) { |
49 | 50 | ||
50 | let vm = this; | 51 | let vm = this; |
51 | 52 | ||
@@ -122,6 +123,16 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, | @@ -122,6 +123,16 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, | ||
122 | addExtension(); | 123 | addExtension(); |
123 | } | 124 | } |
124 | }); | 125 | }); |
126 | + $scope.$on("exportExtensions", function($event, source) { | ||
127 | + if(source.entityId == vm.entityId) { | ||
128 | + vm.exportExtensions(source.entityName); | ||
129 | + } | ||
130 | + }); | ||
131 | + $scope.$on("importExtensions", function($event, source) { | ||
132 | + if(source.entityId == vm.entityId) { | ||
133 | + vm.importExtensions(); | ||
134 | + } | ||
135 | + }); | ||
125 | 136 | ||
126 | function enterFilterMode() { | 137 | function enterFilterMode() { |
127 | vm.query.search = ''; | 138 | vm.query.search = ''; |
@@ -364,4 +375,23 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, | @@ -364,4 +375,23 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, | ||
364 | return num; | 375 | return num; |
365 | } | 376 | } |
366 | } | 377 | } |
378 | + | ||
379 | + vm.importExtensions = function($event) { | ||
380 | + importExport.importExtension($event, {"entityType":vm.entityType, "entityId":vm.entityId, "successFunc":reloadExtensions}); | ||
381 | + }; | ||
382 | + vm.exportExtensions = function(widgetSourceEntityName) { | ||
383 | + if(vm.inWidget) { | ||
384 | + importExport.exportToPc(vm.extensionsJSON, widgetSourceEntityName + '_configuration.json'); | ||
385 | + } else { | ||
386 | + importExport.exportToPc(vm.extensionsJSON, vm.entityName + '_configuration.json'); | ||
387 | + } | ||
388 | + }; | ||
389 | + | ||
390 | + /*change function for widget implementing, like vm.exportExtensions*/ | ||
391 | + vm.exportExtension = function($event, extension) { | ||
392 | + if ($event) { | ||
393 | + $event.stopPropagation(); | ||
394 | + } | ||
395 | + importExport.exportToPc(extension, vm.entityName +'_'+ extension.id +'_configuration.json'); | ||
396 | + }; | ||
367 | } | 397 | } |
@@ -16,11 +16,16 @@ | @@ -16,11 +16,16 @@ | ||
16 | @import '../../scss/constants'; | 16 | @import '../../scss/constants'; |
17 | 17 | ||
18 | 18 | ||
19 | -.extension-table md-input-container .md-errors-spacer { | ||
20 | - min-height: 0; | ||
21 | -} | ||
22 | - | ||
23 | .extension-table { | 19 | .extension-table { |
20 | + | ||
21 | + md-input-container .md-errors-spacer { | ||
22 | + min-height: 0; | ||
23 | + } | ||
24 | + | ||
25 | + /*&.tb-data-table table.md-table tbody tr td.tb-action-cell, | ||
26 | + &.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell { | ||
27 | + width: 114px; | ||
28 | + }*/ | ||
24 | .sync-widget { | 29 | .sync-widget { |
25 | max-height: 90px; | 30 | max-height: 90px; |
26 | overflow: hidden; | 31 | overflow: hidden; |
@@ -31,7 +36,6 @@ | @@ -31,7 +36,6 @@ | ||
31 | } | 36 | } |
32 | } | 37 | } |
33 | 38 | ||
34 | - | ||
35 | .extension__syncStatus--black { | 39 | .extension__syncStatus--black { |
36 | color: #000000!important; | 40 | color: #000000!important; |
37 | } | 41 | } |
@@ -23,6 +23,19 @@ | @@ -23,6 +23,19 @@ | ||
23 | <div class="md-toolbar-tools"> | 23 | <div class="md-toolbar-tools"> |
24 | <span translate>{{ 'extension.extensions' }}</span> | 24 | <span translate>{{ 'extension.extensions' }}</span> |
25 | <span flex></span> | 25 | <span flex></span> |
26 | + | ||
27 | + <md-button class="md-icon-button" ng-click="vm.importExtensions($event)"> | ||
28 | + <md-icon>file_upload</md-icon> | ||
29 | + <md-tooltip md-direction="top"> | ||
30 | + {{ 'extension.import-extensions-configuration' | translate }} | ||
31 | + </md-tooltip> | ||
32 | + </md-button> | ||
33 | + <md-button class="md-icon-button" ng-click="vm.exportExtensions()"> | ||
34 | + <md-icon>file_download</md-icon> | ||
35 | + <md-tooltip md-direction="top"> | ||
36 | + {{ 'extension.export-extensions-configuration' | translate }} | ||
37 | + </md-tooltip> | ||
38 | + </md-button> | ||
26 | <md-button class="md-icon-button" ng-click="vm.addExtension($event)"> | 39 | <md-button class="md-icon-button" ng-click="vm.addExtension($event)"> |
27 | <md-icon>add</md-icon> | 40 | <md-icon>add</md-icon> |
28 | <md-tooltip md-direction="top"> | 41 | <md-tooltip md-direction="top"> |
@@ -111,6 +124,14 @@ | @@ -111,6 +124,14 @@ | ||
111 | <td md-cell>{{ extension.id }}</td> | 124 | <td md-cell>{{ extension.id }}</td> |
112 | <td md-cell>{{ extension.type }}</td> | 125 | <td md-cell>{{ extension.type }}</td> |
113 | <td md-cell class="tb-action-cell"> | 126 | <td md-cell class="tb-action-cell"> |
127 | + | ||
128 | + <!--<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.exportExtension($event, extension)"> | ||
129 | + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">file_download</md-icon> | ||
130 | + <md-tooltip md-direction="top"> | ||
131 | + {{ 'extension.export-extension' | translate }} | ||
132 | + </md-tooltip> | ||
133 | + </md-button>--> | ||
134 | + | ||
114 | <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)"> | 135 | <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)"> |
115 | <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> | 136 | <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> |
116 | <md-tooltip md-direction="top"> | 137 | <md-tooltip md-direction="top"> |
@@ -24,8 +24,9 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; | @@ -24,8 +24,9 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; | ||
24 | /* eslint-disable no-undef, angular/window-service, angular/document-service */ | 24 | /* eslint-disable no-undef, angular/window-service, angular/document-service */ |
25 | 25 | ||
26 | /*@ngInject*/ | 26 | /*@ngInject*/ |
27 | -export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, utils, types, dashboardUtils, | ||
28 | - entityService, dashboardService, pluginService, ruleService, widgetService, toast) { | 27 | +export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, |
28 | + dashboardUtils, entityService, dashboardService, pluginService, ruleService, | ||
29 | + widgetService, toast, attributeService) { | ||
29 | 30 | ||
30 | 31 | ||
31 | var service = { | 32 | var service = { |
@@ -40,8 +41,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, | @@ -40,8 +41,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, | ||
40 | exportWidgetType: exportWidgetType, | 41 | exportWidgetType: exportWidgetType, |
41 | importWidgetType: importWidgetType, | 42 | importWidgetType: importWidgetType, |
42 | exportWidgetsBundle: exportWidgetsBundle, | 43 | exportWidgetsBundle: exportWidgetsBundle, |
43 | - importWidgetsBundle: importWidgetsBundle | ||
44 | - } | 44 | + importWidgetsBundle: importWidgetsBundle, |
45 | + exportExtension: exportExtension, | ||
46 | + importExtension: importExtension, | ||
47 | + exportToPc: exportToPc | ||
48 | + }; | ||
45 | 49 | ||
46 | return service; | 50 | return service; |
47 | 51 | ||
@@ -614,6 +618,84 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, | @@ -614,6 +618,84 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, | ||
614 | return true; | 618 | return true; |
615 | } | 619 | } |
616 | 620 | ||
621 | + | ||
622 | + | ||
623 | + function exportExtension(extensionId) { | ||
624 | + | ||
625 | + getExtension(extensionId) | ||
626 | + .then( | ||
627 | + function success(extension) { | ||
628 | + var name = extension.title; | ||
629 | + name = name.toLowerCase().replace(/\W/g,"_"); | ||
630 | + exportToPc(prepareExport(extension), name + '.json'); | ||
631 | + }, | ||
632 | + function fail(rejection) { | ||
633 | + var message = rejection; | ||
634 | + if (!message) { | ||
635 | + message = $translate.instant('error.unknown-error'); | ||
636 | + } | ||
637 | + toast.showError($translate.instant('extension.export-failed-error', {error: message})); | ||
638 | + } | ||
639 | + ); | ||
640 | + | ||
641 | + function getExtension(extensionId) { | ||
642 | + var deferred = $q.defer(); | ||
643 | + var url = '/api/plugins/telemetry/DEVICE/' + extensionId; | ||
644 | + $http.get(url, null) | ||
645 | + .then(function success(response) { | ||
646 | + deferred.resolve(response.data); | ||
647 | + }, function fail() { | ||
648 | + deferred.reject(); | ||
649 | + }); | ||
650 | + return deferred.promise; | ||
651 | + } | ||
652 | + | ||
653 | + } | ||
654 | + | ||
655 | + function importExtension($event, options) { | ||
656 | + var deferred = $q.defer(); | ||
657 | + openImportDialog($event, 'extension.import-extensions', 'extension.file') | ||
658 | + .then( | ||
659 | + function success(extension) { | ||
660 | + if (!validateImportedExtension(extension)) { | ||
661 | + toast.showError($translate.instant('extension.invalid-file-error')); | ||
662 | + deferred.reject(); | ||
663 | + } else { | ||
664 | + attributeService | ||
665 | + .saveEntityAttributes( | ||
666 | + options.entityType, | ||
667 | + options.entityId, | ||
668 | + types.attributesScope.shared.value, | ||
669 | + [{ | ||
670 | + key: "configuration", | ||
671 | + value: angular.toJson(extension) | ||
672 | + }] | ||
673 | + ) | ||
674 | + .then(function success() { | ||
675 | + options.successFunc(); | ||
676 | + }); | ||
677 | + } | ||
678 | + }, | ||
679 | + function fail() { | ||
680 | + deferred.reject(); | ||
681 | + } | ||
682 | + ); | ||
683 | + return deferred.promise; | ||
684 | + } | ||
685 | + | ||
686 | + function validateImportedExtension(configuration) { | ||
687 | + if (configuration.length) { | ||
688 | + for (let i = 0; i < configuration.length; i++) { | ||
689 | + if (angular.isUndefined(configuration[i].configuration) || angular.isUndefined(configuration[i].id )|| angular.isUndefined(configuration[i].type)) { | ||
690 | + return false; | ||
691 | + } | ||
692 | + } | ||
693 | + } else { | ||
694 | + return false; | ||
695 | + } | ||
696 | + return true; | ||
697 | + } | ||
698 | + | ||
617 | function processEntityAliases(entityAliases, aliasIds) { | 699 | function processEntityAliases(entityAliases, aliasIds) { |
618 | var deferred = $q.defer(); | 700 | var deferred = $q.defer(); |
619 | var missingEntityAliases = {}; | 701 | var missingEntityAliases = {}; |
@@ -847,6 +847,14 @@ export default angular.module('thingsboard.locale', []) | @@ -847,6 +847,14 @@ export default angular.module('thingsboard.locale', []) | ||
847 | "last-sync-time": "Last sync time", | 847 | "last-sync-time": "Last sync time", |
848 | "not-available": "Not available" | 848 | "not-available": "Not available" |
849 | }, | 849 | }, |
850 | + | ||
851 | + "export-extensions-configuration":"Export extensions configuration", | ||
852 | + "import-extensions-configuration":"Import extensions configuration", | ||
853 | + "import-extensions": "Import extensions", | ||
854 | + "import-extension": "Import extension", | ||
855 | + "export-extension": "Export extension", | ||
856 | + "file": "Extensions file", | ||
857 | + "invalid-file-error": "Invalid extension file" | ||
850 | }, | 858 | }, |
851 | "fullscreen": { | 859 | "fullscreen": { |
852 | "expand": "Expand to fullscreen", | 860 | "expand": "Expand to fullscreen", |
@@ -66,7 +66,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | @@ -66,7 +66,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | ||
66 | } | 66 | } |
67 | vm.ctx.widgetTitle = vm.extensionsTitle; | 67 | vm.ctx.widgetTitle = vm.extensionsTitle; |
68 | 68 | ||
69 | - vm.ctx.widgetActions = [vm.addAction, vm.searchAction, vm.refreshAction]; | 69 | + vm.ctx.widgetActions = [vm.importExtensionsAction, vm.exportExtensionsAction, vm.addAction, vm.searchAction, vm.refreshAction]; |
70 | } | 70 | } |
71 | 71 | ||
72 | function updateDatasources() { | 72 | function updateDatasources() { |
@@ -78,7 +78,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | @@ -78,7 +78,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | ||
78 | 78 | ||
79 | vm.changeSelectedSource = function(source) { | 79 | vm.changeSelectedSource = function(source) { |
80 | vm.selectedSource = source; | 80 | vm.selectedSource = source; |
81 | - } | 81 | + }; |
82 | 82 | ||
83 | vm.searchAction = { | 83 | vm.searchAction = { |
84 | name: "action.search", | 84 | name: "action.search", |
@@ -96,7 +96,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | @@ -96,7 +96,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | ||
96 | $scope.$broadcast("refreshExtensions", vm.selectedSource); | 96 | $scope.$broadcast("refreshExtensions", vm.selectedSource); |
97 | }, | 97 | }, |
98 | icon: "refresh" | 98 | icon: "refresh" |
99 | - } | 99 | + }; |
100 | 100 | ||
101 | vm.addAction = { | 101 | vm.addAction = { |
102 | name: "action.add", | 102 | name: "action.add", |
@@ -105,7 +105,25 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | @@ -105,7 +105,25 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { | ||
105 | $scope.$broadcast("addExtension", vm.selectedSource); | 105 | $scope.$broadcast("addExtension", vm.selectedSource); |
106 | }, | 106 | }, |
107 | icon: "add" | 107 | icon: "add" |
108 | - } | 108 | + }; |
109 | + | ||
110 | + vm.exportExtensionsAction = { | ||
111 | + name: "extension.export-extensions-configuration", | ||
112 | + show: true, | ||
113 | + onAction: function() { | ||
114 | + $scope.$broadcast("exportExtensions", vm.selectedSource); | ||
115 | + }, | ||
116 | + icon: "file_download" | ||
117 | + }; | ||
118 | + | ||
119 | + vm.importExtensionsAction = { | ||
120 | + name: "extension.import-extensions-configuration", | ||
121 | + show: true, | ||
122 | + onAction: function() { | ||
123 | + $scope.$broadcast("importExtensions", vm.selectedSource); | ||
124 | + }, | ||
125 | + icon: "file_upload" | ||
126 | + }; | ||
109 | 127 | ||
110 | $scope.$on("filterMode", function($event, mode) { | 128 | $scope.$on("filterMode", function($event, mode) { |
111 | vm.tabsHidden = mode; | 129 | vm.tabsHidden = mode; |