Showing
7 changed files
with
179 additions
and
15 deletions
... | ... | @@ -70,6 +70,7 @@ |
70 | 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 | 71 | <tb-extension-table flex |
72 | 72 | entity-id="vm.grid.operatingItem().id.id" |
73 | + entity-name="vm.grid.operatingItem().name" | |
73 | 74 | entity-type="{{vm.types.entityType.device}}"> |
74 | 75 | </tb-extension-table> |
75 | 76 | </md-tab> | ... | ... |
... | ... | @@ -36,7 +36,8 @@ export default function ExtensionTableDirective() { |
36 | 36 | entityId: '=', |
37 | 37 | entityType: '@', |
38 | 38 | inWidget: '@?', |
39 | - ctx: '=?' | |
39 | + ctx: '=?', | |
40 | + entityName: '=' | |
40 | 41 | }, |
41 | 42 | controller: ExtensionTableController, |
42 | 43 | controllerAs: 'vm', |
... | ... | @@ -45,7 +46,7 @@ export default function ExtensionTableDirective() { |
45 | 46 | } |
46 | 47 | |
47 | 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 | 51 | let vm = this; |
51 | 52 | |
... | ... | @@ -122,6 +123,16 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, |
122 | 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 | 137 | function enterFilterMode() { |
127 | 138 | vm.query.search = ''; |
... | ... | @@ -364,4 +375,23 @@ function ExtensionTableController($scope, $filter, $document, $translate, types, |
364 | 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 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -16,11 +16,16 @@ |
16 | 16 | @import '../../scss/constants'; |
17 | 17 | |
18 | 18 | |
19 | -.extension-table md-input-container .md-errors-spacer { | |
20 | - min-height: 0; | |
21 | -} | |
22 | - | |
23 | 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 | 29 | .sync-widget { |
25 | 30 | max-height: 90px; |
26 | 31 | overflow: hidden; |
... | ... | @@ -31,7 +36,6 @@ |
31 | 36 | } |
32 | 37 | } |
33 | 38 | |
34 | - | |
35 | 39 | .extension__syncStatus--black { |
36 | 40 | color: #000000!important; |
37 | 41 | } | ... | ... |
... | ... | @@ -23,6 +23,19 @@ |
23 | 23 | <div class="md-toolbar-tools"> |
24 | 24 | <span translate>{{ 'extension.extensions' }}</span> |
25 | 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 | 39 | <md-button class="md-icon-button" ng-click="vm.addExtension($event)"> |
27 | 40 | <md-icon>add</md-icon> |
28 | 41 | <md-tooltip md-direction="top"> |
... | ... | @@ -111,6 +124,14 @@ |
111 | 124 | <td md-cell>{{ extension.id }}</td> |
112 | 125 | <td md-cell>{{ extension.type }}</td> |
113 | 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 | 135 | <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" ng-click="vm.editExtension($event, extension)"> |
115 | 136 | <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> |
116 | 137 | <md-tooltip md-direction="top"> | ... | ... |
... | ... | @@ -24,8 +24,9 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; |
24 | 24 | /* eslint-disable no-undef, angular/window-service, angular/document-service */ |
25 | 25 | |
26 | 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 | 32 | var service = { |
... | ... | @@ -40,8 +41,11 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
40 | 41 | exportWidgetType: exportWidgetType, |
41 | 42 | importWidgetType: importWidgetType, |
42 | 43 | exportWidgetsBundle: exportWidgetsBundle, |
43 | - importWidgetsBundle: importWidgetsBundle | |
44 | - } | |
44 | + importWidgetsBundle: importWidgetsBundle, | |
45 | + exportExtension: exportExtension, | |
46 | + importExtension: importExtension, | |
47 | + exportToPc: exportToPc | |
48 | + }; | |
45 | 49 | |
46 | 50 | return service; |
47 | 51 | |
... | ... | @@ -614,6 +618,84 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, |
614 | 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 | 699 | function processEntityAliases(entityAliases, aliasIds) { |
618 | 700 | var deferred = $q.defer(); |
619 | 701 | var missingEntityAliases = {}; | ... | ... |
... | ... | @@ -847,6 +847,14 @@ export default angular.module('thingsboard.locale', []) |
847 | 847 | "last-sync-time": "Last sync time", |
848 | 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 | 859 | "fullscreen": { |
852 | 860 | "expand": "Expand to fullscreen", | ... | ... |
... | ... | @@ -66,7 +66,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { |
66 | 66 | } |
67 | 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 | 72 | function updateDatasources() { |
... | ... | @@ -78,7 +78,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { |
78 | 78 | |
79 | 79 | vm.changeSelectedSource = function(source) { |
80 | 80 | vm.selectedSource = source; |
81 | - } | |
81 | + }; | |
82 | 82 | |
83 | 83 | vm.searchAction = { |
84 | 84 | name: "action.search", |
... | ... | @@ -96,7 +96,7 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { |
96 | 96 | $scope.$broadcast("refreshExtensions", vm.selectedSource); |
97 | 97 | }, |
98 | 98 | icon: "refresh" |
99 | - } | |
99 | + }; | |
100 | 100 | |
101 | 101 | vm.addAction = { |
102 | 102 | name: "action.add", |
... | ... | @@ -105,7 +105,25 @@ function ExtensionsTableWidgetController($scope, $translate, utils) { |
105 | 105 | $scope.$broadcast("addExtension", vm.selectedSource); |
106 | 106 | }, |
107 | 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 | 128 | $scope.$on("filterMode", function($event, mode) { |
111 | 129 | vm.tabsHidden = mode; | ... | ... |