Commit 1305015585c5bc4581d22a7dfbbc7fdb4c912b0e
Committed by
GitHub
Merge pull request #33 from thingsboard/feature/import-export
Import/Export. Bug fixes.
Showing
28 changed files
with
928 additions
and
134 deletions
@@ -54,6 +54,7 @@ | @@ -54,6 +54,7 @@ | ||
54 | "json-schema-defaults": "^0.2.0", | 54 | "json-schema-defaults": "^0.2.0", |
55 | "justgage": "^1.2.2", | 55 | "justgage": "^1.2.2", |
56 | "material-ui": "^0.16.1", | 56 | "material-ui": "^0.16.1", |
57 | + "material-ui-number-input": "^5.0.16", | ||
57 | "md-color-picker": "^0.2.6", | 58 | "md-color-picker": "^0.2.6", |
58 | "mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5", | 59 | "mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5", |
59 | "moment": "^2.15.0", | 60 | "moment": "^2.15.0", |
@@ -88,10 +88,10 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) { | @@ -88,10 +88,10 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) { | ||
88 | return deferred.promise; | 88 | return deferred.promise; |
89 | } | 89 | } |
90 | 90 | ||
91 | - function getDevice(deviceId) { | 91 | + function getDevice(deviceId, ignoreErrors) { |
92 | var deferred = $q.defer(); | 92 | var deferred = $q.defer(); |
93 | var url = '/api/device/' + deviceId; | 93 | var url = '/api/device/' + deviceId; |
94 | - $http.get(url, null).then(function success(response) { | 94 | + $http.get(url, { ignoreErrors: ignoreErrors }).then(function success(response) { |
95 | deferred.resolve(response.data); | 95 | deferred.resolve(response.data); |
96 | }, function fail(response) { | 96 | }, function fail(response) { |
97 | deferred.reject(response.data); | 97 | deferred.reject(response.data); |
@@ -58,8 +58,10 @@ function Dashboard() { | @@ -58,8 +58,10 @@ function Dashboard() { | ||
58 | isMobile: '=', | 58 | isMobile: '=', |
59 | isMobileDisabled: '=?', | 59 | isMobileDisabled: '=?', |
60 | isEditActionEnabled: '=', | 60 | isEditActionEnabled: '=', |
61 | + isExportActionEnabled: '=', | ||
61 | isRemoveActionEnabled: '=', | 62 | isRemoveActionEnabled: '=', |
62 | onEditWidget: '&?', | 63 | onEditWidget: '&?', |
64 | + onExportWidget: '&?', | ||
63 | onRemoveWidget: '&?', | 65 | onRemoveWidget: '&?', |
64 | onWidgetMouseDown: '&?', | 66 | onWidgetMouseDown: '&?', |
65 | onWidgetClicked: '&?', | 67 | onWidgetClicked: '&?', |
@@ -139,6 +141,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast | @@ -139,6 +141,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast | ||
139 | vm.showWidgetTitle = showWidgetTitle; | 141 | vm.showWidgetTitle = showWidgetTitle; |
140 | vm.hasTimewindow = hasTimewindow; | 142 | vm.hasTimewindow = hasTimewindow; |
141 | vm.editWidget = editWidget; | 143 | vm.editWidget = editWidget; |
144 | + vm.exportWidget = exportWidget; | ||
142 | vm.removeWidget = removeWidget; | 145 | vm.removeWidget = removeWidget; |
143 | vm.loading = loading; | 146 | vm.loading = loading; |
144 | 147 | ||
@@ -413,6 +416,16 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast | @@ -413,6 +416,16 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast | ||
413 | } | 416 | } |
414 | } | 417 | } |
415 | 418 | ||
419 | + function exportWidget ($event, widget) { | ||
420 | + resetWidgetClick(); | ||
421 | + if ($event) { | ||
422 | + $event.stopPropagation(); | ||
423 | + } | ||
424 | + if (vm.isExportActionEnabled && vm.onExportWidget) { | ||
425 | + vm.onExportWidget({event: $event, widget: widget}); | ||
426 | + } | ||
427 | + } | ||
428 | + | ||
416 | function removeWidget($event, widget) { | 429 | function removeWidget($event, widget) { |
417 | resetWidgetClick(); | 430 | resetWidgetClick(); |
418 | if ($event) { | 431 | if ($event) { |
@@ -62,6 +62,18 @@ | @@ -62,6 +62,18 @@ | ||
62 | edit | 62 | edit |
63 | </md-icon> | 63 | </md-icon> |
64 | </md-button> | 64 | </md-button> |
65 | + <md-button ng-show="vm.isExportActionEnabled && !vm.isWidgetExpanded" | ||
66 | + ng-disabled="vm.loading()" | ||
67 | + class="md-icon-button md-primary" | ||
68 | + ng-click="vm.exportWidget($event, widget)" | ||
69 | + aria-label="{{ 'widget.export' | translate }}"> | ||
70 | + <md-tooltip md-direction="top"> | ||
71 | + {{ 'widget.export' | translate }} | ||
72 | + </md-tooltip> | ||
73 | + <md-icon class="material-icons"> | ||
74 | + file_download | ||
75 | + </md-icon> | ||
76 | + </md-button> | ||
65 | <md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded" | 77 | <md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded" |
66 | ng-disabled="vm.loading()" | 78 | ng-disabled="vm.loading()" |
67 | class="md-icon-button md-primary" | 79 | class="md-icon-button md-primary" |
@@ -327,6 +327,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra | @@ -327,6 +327,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra | ||
327 | icon: "add" | 327 | icon: "add" |
328 | }; | 328 | }; |
329 | 329 | ||
330 | + vm.addItemActionsOpen = false; | ||
331 | + | ||
332 | + vm.addItemActions = vm.config.addItemActions || []; | ||
333 | + | ||
330 | vm.onGridInited = vm.config.onGridInited || function () { | 334 | vm.onGridInited = vm.config.onGridInited || function () { |
331 | }; | 335 | }; |
332 | 336 |
@@ -79,7 +79,7 @@ | @@ -79,7 +79,7 @@ | ||
79 | </tb-details-sidenav> | 79 | </tb-details-sidenav> |
80 | </section> | 80 | </section> |
81 | 81 | ||
82 | -<section layout="row" layout-wrap class="tb-footer-buttons md-fab "> | 82 | +<section layout="row" layout-wrap class="tb-footer-buttons md-fab " layout-align="start end"> |
83 | <md-button ng-disabled="loading" ng-show="vm.items.selectedCount > 0" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-repeat="groupAction in vm.groupActionsList" | 83 | <md-button ng-disabled="loading" ng-show="vm.items.selectedCount > 0" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-repeat="groupAction in vm.groupActionsList" |
84 | ng-click="groupAction.onAction($event, vm.items)" aria-label="{{ groupAction.name() }}"> | 84 | ng-click="groupAction.onAction($event, vm.items)" aria-label="{{ groupAction.name() }}"> |
85 | <md-tooltip md-direction="top"> | 85 | <md-tooltip md-direction="top"> |
@@ -93,10 +93,29 @@ | @@ -93,10 +93,29 @@ | ||
93 | </md-tooltip> | 93 | </md-tooltip> |
94 | <ng-md-icon icon="arrow_drop_up"></ng-md-icon> | 94 | <ng-md-icon icon="arrow_drop_up"></ng-md-icon> |
95 | </md-button> | 95 | </md-button> |
96 | - <md-button ng-disabled="loading" ng-if="vm.addItemAction.name()" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addItemAction.onAction($event)" aria-label="{{ vm.addItemAction.name() }}" > | 96 | + <md-button ng-disabled="loading" ng-if="vm.addItemAction.name() && vm.addItemActions.length == 0" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addItemAction.onAction($event)" aria-label="{{ vm.addItemAction.name() }}" > |
97 | <md-tooltip md-direction="top"> | 97 | <md-tooltip md-direction="top"> |
98 | {{ vm.addItemAction.details() }} | 98 | {{ vm.addItemAction.details() }} |
99 | </md-tooltip> | 99 | </md-tooltip> |
100 | <ng-md-icon icon="{{ vm.addItemAction.icon }}"></ng-md-icon> | 100 | <ng-md-icon icon="{{ vm.addItemAction.icon }}"></ng-md-icon> |
101 | </md-button> | 101 | </md-button> |
102 | + <md-fab-speed-dial ng-disabled="loading" ng-if="vm.addItemAction.name() && vm.addItemActions.length > 0" md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up" ng-if="vm.addItemAction.name()"> | ||
103 | + <md-fab-trigger> | ||
104 | + <md-button ng-disabled="loading" class="tb-btn-footer md-accent md-hue-2 md-fab" aria-label="{{ vm.addItemAction.name() }}" > | ||
105 | + <md-tooltip md-direction="top"> | ||
106 | + {{ vm.addItemAction.details() }} | ||
107 | + </md-tooltip> | ||
108 | + <ng-md-icon icon="{{ vm.addItemAction.icon }}"></ng-md-icon> | ||
109 | + </md-button> | ||
110 | + </md-fab-trigger> | ||
111 | + <md-fab-actions> | ||
112 | + <md-button ng-disabled="loading" class="md-accent md-hue-2 md-fab" ng-repeat="addItemAction in vm.addItemActions" | ||
113 | + ng-click="addItemAction.onAction($event)" aria-label="{{ addItemAction.name() }}" > | ||
114 | + <md-tooltip md-direction="top"> | ||
115 | + {{ addItemAction.details() }} | ||
116 | + </md-tooltip> | ||
117 | + <ng-md-icon icon="{{addItemAction.icon}}"></ng-md-icon> | ||
118 | + </md-button> | ||
119 | + </md-fab-actions> | ||
120 | + </md-fab-speed-dial> | ||
102 | </section> | 121 | </section> |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | import React from 'react'; | 16 | import React from 'react'; |
17 | import ThingsboardBaseComponent from './json-form-base-component.jsx'; | 17 | import ThingsboardBaseComponent from './json-form-base-component.jsx'; |
18 | -import TextField from 'material-ui/TextField'; | 18 | +import NumberInput from 'material-ui-number-input'; |
19 | 19 | ||
20 | class ThingsboardNumber extends React.Component { | 20 | class ThingsboardNumber extends React.Component { |
21 | 21 | ||
@@ -63,16 +63,18 @@ class ThingsboardNumber extends React.Component { | @@ -63,16 +63,18 @@ class ThingsboardNumber extends React.Component { | ||
63 | if (this.state.focused) { | 63 | if (this.state.focused) { |
64 | fieldClass += " tb-focused"; | 64 | fieldClass += " tb-focused"; |
65 | } | 65 | } |
66 | + var value = this.state.lastSuccessfulValue; | ||
67 | + value = Number(value); | ||
66 | 68 | ||
67 | return ( | 69 | return ( |
68 | - <TextField | 70 | + <NumberInput |
69 | className={fieldClass} | 71 | className={fieldClass} |
70 | - type={this.props.form.type} | 72 | + strategy="allow" |
71 | floatingLabelText={this.props.form.title} | 73 | floatingLabelText={this.props.form.title} |
72 | hintText={this.props.form.placeholder} | 74 | hintText={this.props.form.placeholder} |
73 | errorText={this.props.error} | 75 | errorText={this.props.error} |
74 | onChange={this.preValidationCheck} | 76 | onChange={this.preValidationCheck} |
75 | - defaultValue={this.state.lastSuccessfulValue} | 77 | + defaultValue={value} |
76 | ref="numberField" | 78 | ref="numberField" |
77 | disabled={this.props.form.readonly} | 79 | disabled={this.props.form.readonly} |
78 | onFocus={this.onFocus} | 80 | onFocus={this.onFocus} |
@@ -102,10 +102,12 @@ export default function AddWidgetController($scope, widgetService, deviceService | @@ -102,10 +102,12 @@ export default function AddWidgetController($scope, widgetService, deviceService | ||
102 | controllerAs: 'vm', | 102 | controllerAs: 'vm', |
103 | templateUrl: deviceAliasesTemplate, | 103 | templateUrl: deviceAliasesTemplate, |
104 | locals: { | 104 | locals: { |
105 | - deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases), | ||
106 | - aliasToWidgetsMap: null, | ||
107 | - isSingleDevice: true, | ||
108 | - singleDeviceAlias: singleDeviceAlias | 105 | + config: { |
106 | + deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases), | ||
107 | + widgets: null, | ||
108 | + isSingleDevice: true, | ||
109 | + singleDeviceAlias: singleDeviceAlias | ||
110 | + } | ||
109 | }, | 111 | }, |
110 | parent: angular.element($document[0].body), | 112 | parent: angular.element($document[0].body), |
111 | fullscreen: true, | 113 | fullscreen: true, |
@@ -17,6 +17,7 @@ | @@ -17,6 +17,7 @@ | ||
17 | --> | 17 | --> |
18 | <md-button ng-click="onAssignToCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button> | 18 | <md-button ng-click="onAssignToCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button> |
19 | <md-button ng-click="onUnassignFromCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'customer'" class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button> | 19 | <md-button ng-click="onUnassignFromCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'customer'" class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button> |
20 | +<md-button ng-click="onExportDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> | ||
20 | <md-button ng-click="onDeleteDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> | 21 | <md-button ng-click="onDeleteDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> |
21 | 22 | ||
22 | <md-content class="md-padding" layout="column"> | 23 | <md-content class="md-padding" layout="column"> |
@@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html'; | @@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html'; | ||
23 | 23 | ||
24 | /*@ngInject*/ | 24 | /*@ngInject*/ |
25 | export default function DashboardController(types, widgetService, userService, | 25 | export default function DashboardController(types, widgetService, userService, |
26 | - dashboardService, itembuffer, hotkeys, $window, $rootScope, | 26 | + dashboardService, itembuffer, importExport, hotkeys, $window, $rootScope, |
27 | $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) { | 27 | $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) { |
28 | 28 | ||
29 | var user = userService.getCurrentUser(); | 29 | var user = userService.getCurrentUser(); |
@@ -53,6 +53,8 @@ export default function DashboardController(types, widgetService, userService, | @@ -53,6 +53,8 @@ export default function DashboardController(types, widgetService, userService, | ||
53 | vm.prepareDashboardContextMenu = prepareDashboardContextMenu; | 53 | vm.prepareDashboardContextMenu = prepareDashboardContextMenu; |
54 | vm.prepareWidgetContextMenu = prepareWidgetContextMenu; | 54 | vm.prepareWidgetContextMenu = prepareWidgetContextMenu; |
55 | vm.editWidget = editWidget; | 55 | vm.editWidget = editWidget; |
56 | + vm.exportWidget = exportWidget; | ||
57 | + vm.importWidget = importWidget; | ||
56 | vm.isTenantAdmin = isTenantAdmin; | 58 | vm.isTenantAdmin = isTenantAdmin; |
57 | vm.loadDashboard = loadDashboard; | 59 | vm.loadDashboard = loadDashboard; |
58 | vm.noData = noData; | 60 | vm.noData = noData; |
@@ -210,44 +212,17 @@ export default function DashboardController(types, widgetService, userService, | @@ -210,44 +212,17 @@ export default function DashboardController(types, widgetService, userService, | ||
210 | } | 212 | } |
211 | 213 | ||
212 | function openDeviceAliases($event) { | 214 | function openDeviceAliases($event) { |
213 | - var aliasToWidgetsMap = {}; | ||
214 | - var widgetsTitleList; | ||
215 | - for (var w in vm.widgets) { | ||
216 | - var widget = vm.widgets[w]; | ||
217 | - if (widget.type === types.widgetType.rpc.value) { | ||
218 | - if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) { | ||
219 | - var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0]; | ||
220 | - widgetsTitleList = aliasToWidgetsMap[targetDeviceAliasId]; | ||
221 | - if (!widgetsTitleList) { | ||
222 | - widgetsTitleList = []; | ||
223 | - aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList; | ||
224 | - } | ||
225 | - widgetsTitleList.push(widget.config.title); | ||
226 | - } | ||
227 | - } else { | ||
228 | - for (var i in widget.config.datasources) { | ||
229 | - var datasource = widget.config.datasources[i]; | ||
230 | - if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { | ||
231 | - widgetsTitleList = aliasToWidgetsMap[datasource.deviceAliasId]; | ||
232 | - if (!widgetsTitleList) { | ||
233 | - widgetsTitleList = []; | ||
234 | - aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList; | ||
235 | - } | ||
236 | - widgetsTitleList.push(widget.config.title); | ||
237 | - } | ||
238 | - } | ||
239 | - } | ||
240 | - } | ||
241 | - | ||
242 | $mdDialog.show({ | 215 | $mdDialog.show({ |
243 | controller: 'DeviceAliasesController', | 216 | controller: 'DeviceAliasesController', |
244 | controllerAs: 'vm', | 217 | controllerAs: 'vm', |
245 | templateUrl: deviceAliasesTemplate, | 218 | templateUrl: deviceAliasesTemplate, |
246 | locals: { | 219 | locals: { |
247 | - deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases), | ||
248 | - aliasToWidgetsMap: aliasToWidgetsMap, | ||
249 | - isSingleDevice: false, | ||
250 | - singleDeviceAlias: null | 220 | + config: { |
221 | + deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases), | ||
222 | + widgets: vm.widgets, | ||
223 | + isSingleDevice: false, | ||
224 | + singleDeviceAlias: null | ||
225 | + } | ||
251 | }, | 226 | }, |
252 | parent: angular.element($document[0].body), | 227 | parent: angular.element($document[0].body), |
253 | skipHide: true, | 228 | skipHide: true, |
@@ -300,6 +275,16 @@ export default function DashboardController(types, widgetService, userService, | @@ -300,6 +275,16 @@ export default function DashboardController(types, widgetService, userService, | ||
300 | } | 275 | } |
301 | } | 276 | } |
302 | 277 | ||
278 | + function exportWidget($event, widget) { | ||
279 | + $event.stopPropagation(); | ||
280 | + importExport.exportWidget(vm.dashboard, widget); | ||
281 | + } | ||
282 | + | ||
283 | + function importWidget($event) { | ||
284 | + $event.stopPropagation(); | ||
285 | + importExport.importWidget($event, vm.dashboard); | ||
286 | + } | ||
287 | + | ||
303 | function widgetMouseDown($event, widget) { | 288 | function widgetMouseDown($event, widget) { |
304 | if (vm.isEdit && !vm.isEditingWidget) { | 289 | if (vm.isEdit && !vm.isEditingWidget) { |
305 | vm.dashboardContainer.selectWidget(widget, 0); | 290 | vm.dashboardContainer.selectWidget(widget, 0); |
@@ -438,48 +423,7 @@ export default function DashboardController(types, widgetService, userService, | @@ -438,48 +423,7 @@ export default function DashboardController(types, widgetService, userService, | ||
438 | } | 423 | } |
439 | 424 | ||
440 | function copyWidget($event, widget) { | 425 | function copyWidget($event, widget) { |
441 | - var aliasesInfo = { | ||
442 | - datasourceAliases: {}, | ||
443 | - targetDeviceAliases: {} | ||
444 | - }; | ||
445 | - var originalColumns = 24; | ||
446 | - if (vm.dashboard.configuration.gridSettings && | ||
447 | - vm.dashboard.configuration.gridSettings.columns) { | ||
448 | - originalColumns = vm.dashboard.configuration.gridSettings.columns; | ||
449 | - } | ||
450 | - if (widget.config && vm.dashboard.configuration | ||
451 | - && vm.dashboard.configuration.deviceAliases) { | ||
452 | - var deviceAlias; | ||
453 | - if (widget.config.datasources) { | ||
454 | - for (var i=0;i<widget.config.datasources.length;i++) { | ||
455 | - var datasource = widget.config.datasources[i]; | ||
456 | - if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { | ||
457 | - deviceAlias = vm.dashboard.configuration.deviceAliases[datasource.deviceAliasId]; | ||
458 | - if (deviceAlias) { | ||
459 | - aliasesInfo.datasourceAliases[i] = { | ||
460 | - aliasName: deviceAlias.alias, | ||
461 | - deviceId: deviceAlias.deviceId | ||
462 | - } | ||
463 | - } | ||
464 | - } | ||
465 | - } | ||
466 | - } | ||
467 | - if (widget.config.targetDeviceAliasIds) { | ||
468 | - for (i=0;i<widget.config.targetDeviceAliasIds.length;i++) { | ||
469 | - var targetDeviceAliasId = widget.config.targetDeviceAliasIds[i]; | ||
470 | - if (targetDeviceAliasId) { | ||
471 | - deviceAlias = vm.dashboard.configuration.deviceAliases[targetDeviceAliasId]; | ||
472 | - if (deviceAlias) { | ||
473 | - aliasesInfo.targetDeviceAliases[i] = { | ||
474 | - aliasName: deviceAlias.alias, | ||
475 | - deviceId: deviceAlias.deviceId | ||
476 | - } | ||
477 | - } | ||
478 | - } | ||
479 | - } | ||
480 | - } | ||
481 | - } | ||
482 | - itembuffer.copyWidget(widget, aliasesInfo, originalColumns); | 426 | + itembuffer.copyWidget(vm.dashboard, widget); |
483 | } | 427 | } |
484 | 428 | ||
485 | function helpLinkIdForWidgetType() { | 429 | function helpLinkIdForWidgetType() { |
@@ -36,6 +36,7 @@ export default function DashboardDirective($compile, $templateCache) { | @@ -36,6 +36,7 @@ export default function DashboardDirective($compile, $templateCache) { | ||
36 | theForm: '=', | 36 | theForm: '=', |
37 | onAssignToCustomer: '&', | 37 | onAssignToCustomer: '&', |
38 | onUnassignFromCustomer: '&', | 38 | onUnassignFromCustomer: '&', |
39 | + onExportDashboard: '&', | ||
39 | onDeleteDashboard: '&' | 40 | onDeleteDashboard: '&' |
40 | } | 41 | } |
41 | }; | 42 | }; |
@@ -80,8 +80,10 @@ | @@ -80,8 +80,10 @@ | ||
80 | is-mobile="vm.forceDashboardMobileMode" | 80 | is-mobile="vm.forceDashboardMobileMode" |
81 | is-mobile-disabled="vm.widgetEditMode" | 81 | is-mobile-disabled="vm.widgetEditMode" |
82 | is-edit-action-enabled="vm.isEdit || vm.widgetEditMode" | 82 | is-edit-action-enabled="vm.isEdit || vm.widgetEditMode" |
83 | + is-export-action-enabled="vm.isEdit && !vm.widgetEditMode" | ||
83 | is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode" | 84 | is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode" |
84 | on-edit-widget="vm.editWidget(event, widget)" | 85 | on-edit-widget="vm.editWidget(event, widget)" |
86 | + on-export-widget="vm.exportWidget(event, widget)" | ||
85 | on-widget-mouse-down="vm.widgetMouseDown(event, widget)" | 87 | on-widget-mouse-down="vm.widgetMouseDown(event, widget)" |
86 | on-widget-clicked="vm.widgetClicked(event, widget)" | 88 | on-widget-clicked="vm.widgetClicked(event, widget)" |
87 | on-widget-context-menu="vm.widgetContextMenu(event, widget)" | 89 | on-widget-context-menu="vm.widgetContextMenu(event, widget)" |
@@ -180,15 +182,38 @@ | @@ -180,15 +182,38 @@ | ||
180 | </div> | 182 | </div> |
181 | </tb-details-sidenav> | 183 | </tb-details-sidenav> |
182 | <!-- </section> --> | 184 | <!-- </section> --> |
183 | - <section layout="row" layout-wrap class="tb-footer-buttons md-fab"> | ||
184 | - <md-button ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode" | ||
185 | - class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)" | ||
186 | - aria-label="{{ 'dashboard.add-widget' | translate }}"> | ||
187 | - <md-tooltip md-direction="top"> | ||
188 | - {{ 'dashboard.add-widget' | translate }} | ||
189 | - </md-tooltip> | ||
190 | - <ng-md-icon icon="add"></ng-md-icon> | ||
191 | - </md-button> | 185 | + <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end"> |
186 | + <md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode" | ||
187 | + md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up"> | ||
188 | + <md-fab-trigger> | ||
189 | + <md-button ng-disabled="loading" | ||
190 | + class="tb-btn-footer md-accent md-hue-2 md-fab" | ||
191 | + aria-label="{{ 'dashboard.add-widget' | translate }}"> | ||
192 | + <md-tooltip md-direction="top"> | ||
193 | + {{ 'dashboard.add-widget' | translate }} | ||
194 | + </md-tooltip> | ||
195 | + <ng-md-icon icon="add"></ng-md-icon> | ||
196 | + </md-button> | ||
197 | + </md-fab-trigger> | ||
198 | + <md-fab-actions> | ||
199 | + <md-button ng-disabled="loading" | ||
200 | + class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)" | ||
201 | + aria-label="{{ 'action.create' | translate }}"> | ||
202 | + <md-tooltip md-direction="top"> | ||
203 | + {{ 'dashboard.create-new-widget' | translate }} | ||
204 | + </md-tooltip> | ||
205 | + <ng-md-icon icon="insert_drive_file"></ng-md-icon> | ||
206 | + </md-button> | ||
207 | + <md-button ng-disabled="loading" | ||
208 | + class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidget($event)" | ||
209 | + aria-label="{{ 'action.import' | translate }}"> | ||
210 | + <md-tooltip md-direction="top"> | ||
211 | + {{ 'dashboard.import-widget' | translate }} | ||
212 | + </md-tooltip> | ||
213 | + <ng-md-icon icon="file_upload"></ng-md-icon> | ||
214 | + </md-button> | ||
215 | + </md-fab-actions> | ||
216 | + </md-fab-speed-dial> | ||
192 | <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading && !vm.widgetEditMode" ng-disabled="loading" | 217 | <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading && !vm.widgetEditMode" ng-disabled="loading" |
193 | class="tb-btn-footer md-accent md-hue-2 md-fab" | 218 | class="tb-btn-footer md-accent md-hue-2 md-fab" |
194 | aria-label="{{ 'action.apply' | translate }}" | 219 | aria-label="{{ 'action.apply' | translate }}" |
@@ -23,7 +23,7 @@ import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.ht | @@ -23,7 +23,7 @@ import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.ht | ||
23 | /* eslint-enable import/no-unresolved, import/default */ | 23 | /* eslint-enable import/no-unresolved, import/default */ |
24 | 24 | ||
25 | /*@ngInject*/ | 25 | /*@ngInject*/ |
26 | -export default function DashboardsController(userService, dashboardService, customerService, $scope, $controller, $state, $stateParams, $mdDialog, $document, $q, $translate) { | 26 | +export default function DashboardsController(userService, dashboardService, customerService, importExport, $scope, $controller, $state, $stateParams, $mdDialog, $document, $q, $translate) { |
27 | 27 | ||
28 | var customerId = $stateParams.customerId; | 28 | var customerId = $stateParams.customerId; |
29 | 29 | ||
@@ -86,6 +86,7 @@ export default function DashboardsController(userService, dashboardService, cust | @@ -86,6 +86,7 @@ export default function DashboardsController(userService, dashboardService, cust | ||
86 | 86 | ||
87 | vm.assignToCustomer = assignToCustomer; | 87 | vm.assignToCustomer = assignToCustomer; |
88 | vm.unassignFromCustomer = unassignFromCustomer; | 88 | vm.unassignFromCustomer = unassignFromCustomer; |
89 | + vm.exportDashboard = exportDashboard; | ||
89 | 90 | ||
90 | initController(); | 91 | initController(); |
91 | 92 | ||
@@ -115,6 +116,14 @@ export default function DashboardsController(userService, dashboardService, cust | @@ -115,6 +116,14 @@ export default function DashboardsController(userService, dashboardService, cust | ||
115 | dashboardActionsList.push( | 116 | dashboardActionsList.push( |
116 | { | 117 | { |
117 | onAction: function ($event, item) { | 118 | onAction: function ($event, item) { |
119 | + exportDashboard($event, item); | ||
120 | + }, | ||
121 | + name: function() { $translate.instant('action.export') }, | ||
122 | + details: function() { return $translate.instant('dashboard.export') }, | ||
123 | + icon: "file_download" | ||
124 | + }, | ||
125 | + { | ||
126 | + onAction: function ($event, item) { | ||
118 | assignToCustomer($event, [ item.id.id ]); | 127 | assignToCustomer($event, [ item.id.id ]); |
119 | }, | 128 | }, |
120 | name: function() { return $translate.instant('action.assign') }, | 129 | name: function() { return $translate.instant('action.assign') }, |
@@ -158,7 +167,27 @@ export default function DashboardsController(userService, dashboardService, cust | @@ -158,7 +167,27 @@ export default function DashboardsController(userService, dashboardService, cust | ||
158 | } | 167 | } |
159 | ); | 168 | ); |
160 | 169 | ||
161 | - | 170 | + vm.dashboardGridConfig.addItemActions = []; |
171 | + vm.dashboardGridConfig.addItemActions.push({ | ||
172 | + onAction: function ($event) { | ||
173 | + vm.grid.addItem($event); | ||
174 | + }, | ||
175 | + name: function() { return $translate.instant('action.create') }, | ||
176 | + details: function() { return $translate.instant('dashboard.create-new-dashboard') }, | ||
177 | + icon: "insert_drive_file" | ||
178 | + }); | ||
179 | + vm.dashboardGridConfig.addItemActions.push({ | ||
180 | + onAction: function ($event) { | ||
181 | + importExport.importDashboard($event).then( | ||
182 | + function() { | ||
183 | + vm.grid.refreshList(); | ||
184 | + } | ||
185 | + ); | ||
186 | + }, | ||
187 | + name: function() { return $translate.instant('action.import') }, | ||
188 | + details: function() { return $translate.instant('dashboard.import') }, | ||
189 | + icon: "file_upload" | ||
190 | + }); | ||
162 | } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') { | 191 | } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') { |
163 | fetchDashboardsFunction = function (pageLink) { | 192 | fetchDashboardsFunction = function (pageLink) { |
164 | return dashboardService.getCustomerDashboards(customerId, pageLink); | 193 | return dashboardService.getCustomerDashboards(customerId, pageLink); |
@@ -344,6 +373,11 @@ export default function DashboardsController(userService, dashboardService, cust | @@ -344,6 +373,11 @@ export default function DashboardsController(userService, dashboardService, cust | ||
344 | }); | 373 | }); |
345 | } | 374 | } |
346 | 375 | ||
376 | + function exportDashboard($event, dashboard) { | ||
377 | + $event.stopPropagation(); | ||
378 | + importExport.exportDashboard(dashboard.id.id); | ||
379 | + } | ||
380 | + | ||
347 | function unassignDashboardsFromCustomer($event, items) { | 381 | function unassignDashboardsFromCustomer($event, items) { |
348 | var confirm = $mdDialog.confirm() | 382 | var confirm = $mdDialog.confirm() |
349 | .targetEvent($event) | 383 | .targetEvent($event) |
@@ -25,5 +25,6 @@ | @@ -25,5 +25,6 @@ | ||
25 | the-form="vm.grid.detailsForm" | 25 | the-form="vm.grid.detailsForm" |
26 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" | 26 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" |
27 | on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)" | 27 | on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)" |
28 | + on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" | ||
28 | on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> | 29 | on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> |
29 | </tb-grid> | 30 | </tb-grid> |
@@ -17,16 +17,18 @@ import './device-aliases.scss'; | @@ -17,16 +17,18 @@ import './device-aliases.scss'; | ||
17 | 17 | ||
18 | /*@ngInject*/ | 18 | /*@ngInject*/ |
19 | export default function DeviceAliasesController(deviceService, toast, $scope, $mdDialog, $document, $q, $translate, | 19 | export default function DeviceAliasesController(deviceService, toast, $scope, $mdDialog, $document, $q, $translate, |
20 | - deviceAliases, aliasToWidgetsMap, isSingleDevice, singleDeviceAlias) { | 20 | + types, config) { |
21 | 21 | ||
22 | var vm = this; | 22 | var vm = this; |
23 | 23 | ||
24 | - vm.isSingleDevice = isSingleDevice; | ||
25 | - vm.singleDeviceAlias = singleDeviceAlias; | 24 | + vm.isSingleDevice = config.isSingleDevice; |
25 | + vm.singleDeviceAlias = config.singleDeviceAlias; | ||
26 | vm.deviceAliases = []; | 26 | vm.deviceAliases = []; |
27 | - vm.aliasToWidgetsMap = aliasToWidgetsMap; | ||
28 | vm.singleDevice = null; | 27 | vm.singleDevice = null; |
29 | vm.singleDeviceSearchText = ''; | 28 | vm.singleDeviceSearchText = ''; |
29 | + vm.title = config.customTitle ? config.customTitle : 'device.aliases'; | ||
30 | + vm.disableAdd = config.disableAdd; | ||
31 | + vm.aliasToWidgetsMap = {}; | ||
30 | 32 | ||
31 | vm.addAlias = addAlias; | 33 | vm.addAlias = addAlias; |
32 | vm.cancel = cancel; | 34 | vm.cancel = cancel; |
@@ -39,9 +41,48 @@ export default function DeviceAliasesController(deviceService, toast, $scope, $m | @@ -39,9 +41,48 @@ export default function DeviceAliasesController(deviceService, toast, $scope, $m | ||
39 | initController(); | 41 | initController(); |
40 | 42 | ||
41 | function initController() { | 43 | function initController() { |
42 | - for (var aliasId in deviceAliases) { | ||
43 | - var alias = deviceAliases[aliasId].alias; | ||
44 | - var deviceId = deviceAliases[aliasId].deviceId; | 44 | + var aliasId; |
45 | + if (config.widgets) { | ||
46 | + var widgetsTitleList, widget; | ||
47 | + if (config.isSingleWidget && config.widgets.length == 1) { | ||
48 | + widget = config.widgets[0]; | ||
49 | + widgetsTitleList = [widget.config.title]; | ||
50 | + for (aliasId in config.deviceAliases) { | ||
51 | + vm.aliasToWidgetsMap[aliasId] = widgetsTitleList; | ||
52 | + } | ||
53 | + } else { | ||
54 | + for (var w in config.widgets) { | ||
55 | + widget = config.widgets[w]; | ||
56 | + if (widget.type === types.widgetType.rpc.value) { | ||
57 | + if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) { | ||
58 | + var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0]; | ||
59 | + widgetsTitleList = vm.aliasToWidgetsMap[targetDeviceAliasId]; | ||
60 | + if (!widgetsTitleList) { | ||
61 | + widgetsTitleList = []; | ||
62 | + vm.aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList; | ||
63 | + } | ||
64 | + widgetsTitleList.push(widget.config.title); | ||
65 | + } | ||
66 | + } else { | ||
67 | + for (var i in widget.config.datasources) { | ||
68 | + var datasource = widget.config.datasources[i]; | ||
69 | + if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { | ||
70 | + widgetsTitleList = vm.aliasToWidgetsMap[datasource.deviceAliasId]; | ||
71 | + if (!widgetsTitleList) { | ||
72 | + widgetsTitleList = []; | ||
73 | + vm.aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList; | ||
74 | + } | ||
75 | + widgetsTitleList.push(widget.config.title); | ||
76 | + } | ||
77 | + } | ||
78 | + } | ||
79 | + } | ||
80 | + } | ||
81 | + } | ||
82 | + | ||
83 | + for (aliasId in config.deviceAliases) { | ||
84 | + var alias = config.deviceAliases[aliasId].alias; | ||
85 | + var deviceId = config.deviceAliases[aliasId].deviceId; | ||
45 | var deviceAlias = {id: aliasId, alias: alias, device: null, changed: false, searchText: ''}; | 86 | var deviceAlias = {id: aliasId, alias: alias, device: null, changed: false, searchText: ''}; |
46 | if (deviceId) { | 87 | if (deviceId) { |
47 | fetchAliasDevice(deviceAlias, deviceId); | 88 | fetchAliasDevice(deviceAlias, deviceId); |
@@ -15,11 +15,11 @@ | @@ -15,11 +15,11 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<md-dialog style="width: 700px;" aria-label="{{ 'device.aliases' | translate }}"> | 18 | +<md-dialog style="width: 700px;" aria-label="{{ vm.title | translate }}"> |
19 | <form name="theForm" ng-submit="vm.save()"> | 19 | <form name="theForm" ng-submit="vm.save()"> |
20 | <md-toolbar> | 20 | <md-toolbar> |
21 | <div class="md-toolbar-tools"> | 21 | <div class="md-toolbar-tools"> |
22 | - <h2>{{ vm.isSingleDevice ? ('device.select-device-for-alias' | translate:vm.singleDeviceAlias ) : ('device.aliases' | translate) }}</h2> | 22 | + <h2>{{ vm.isSingleDevice ? ('device.select-device-for-alias' | translate:vm.singleDeviceAlias ) : (vm.title | translate) }}</h2> |
23 | <span flex></span> | 23 | <span flex></span> |
24 | <md-button class="md-icon-button" ng-click="vm.cancel()"> | 24 | <md-button class="md-icon-button" ng-click="vm.cancel()"> |
25 | <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | 25 | <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
@@ -109,7 +109,7 @@ | @@ -109,7 +109,7 @@ | ||
109 | </div> | 109 | </div> |
110 | </div> | 110 | </div> |
111 | </div> | 111 | </div> |
112 | - <div ng-show="!vm.isSingleDevice" style="padding-bottom: 10px;"> | 112 | + <div ng-show="!vm.isSingleDevice && !vm.disableAdd" style="padding-bottom: 10px;"> |
113 | <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}"> | 113 | <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}"> |
114 | <md-tooltip md-direction="top"> | 114 | <md-tooltip md-direction="top"> |
115 | {{ 'device.add-alias' | translate }} | 115 | {{ 'device.add-alias' | translate }} |
@@ -76,10 +76,12 @@ export default function EditWidgetDirective($compile, $templateCache, widgetServ | @@ -76,10 +76,12 @@ export default function EditWidgetDirective($compile, $templateCache, widgetServ | ||
76 | controllerAs: 'vm', | 76 | controllerAs: 'vm', |
77 | templateUrl: deviceAliasesTemplate, | 77 | templateUrl: deviceAliasesTemplate, |
78 | locals: { | 78 | locals: { |
79 | - deviceAliases: angular.copy(scope.dashboard.configuration.deviceAliases), | ||
80 | - aliasToWidgetsMap: null, | ||
81 | - isSingleDevice: true, | ||
82 | - singleDeviceAlias: singleDeviceAlias | 79 | + config: { |
80 | + deviceAliases: angular.copy(scope.dashboard.configuration.deviceAliases), | ||
81 | + widgets: null, | ||
82 | + isSingleDevice: true, | ||
83 | + singleDeviceAlias: singleDeviceAlias | ||
84 | + } | ||
83 | }, | 85 | }, |
84 | parent: angular.element($document[0].body), | 86 | parent: angular.element($document[0].body), |
85 | fullscreen: true, | 87 | fullscreen: true, |
@@ -30,6 +30,7 @@ import thingsboardExpandFullscreen from '../components/expand-fullscreen.directi | @@ -30,6 +30,7 @@ import thingsboardExpandFullscreen from '../components/expand-fullscreen.directi | ||
30 | import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive'; | 30 | import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive'; |
31 | import thingsboardTypes from '../common/types.constant'; | 31 | import thingsboardTypes from '../common/types.constant'; |
32 | import thingsboardItemBuffer from '../services/item-buffer.service'; | 32 | import thingsboardItemBuffer from '../services/item-buffer.service'; |
33 | +import thingsboardImportExport from '../import-export'; | ||
33 | 34 | ||
34 | import DashboardRoutes from './dashboard.routes'; | 35 | import DashboardRoutes from './dashboard.routes'; |
35 | import DashboardsController from './dashboards.controller'; | 36 | import DashboardsController from './dashboards.controller'; |
@@ -47,6 +48,7 @@ export default angular.module('thingsboard.dashboard', [ | @@ -47,6 +48,7 @@ export default angular.module('thingsboard.dashboard', [ | ||
47 | gridster.name, | 48 | gridster.name, |
48 | thingsboardTypes, | 49 | thingsboardTypes, |
49 | thingsboardItemBuffer, | 50 | thingsboardItemBuffer, |
51 | + thingsboardImportExport, | ||
50 | thingsboardGrid, | 52 | thingsboardGrid, |
51 | thingsboardApiWidget, | 53 | thingsboardApiWidget, |
52 | thingsboardApiUser, | 54 | thingsboardApiUser, |
@@ -148,6 +148,7 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { | @@ -148,6 +148,7 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { | ||
148 | $rootScope.loading = false; | 148 | $rootScope.loading = false; |
149 | } | 149 | } |
150 | var unhandled = false; | 150 | var unhandled = false; |
151 | + var ignoreErrors = rejection.config.ignoreErrors; | ||
151 | if (rejection.refreshTokenPending || rejection.status === 401) { | 152 | if (rejection.refreshTokenPending || rejection.status === 401) { |
152 | var errorCode = rejectionErrorCode(rejection); | 153 | var errorCode = rejectionErrorCode(rejection); |
153 | if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) { | 154 | if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) { |
@@ -156,13 +157,17 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { | @@ -156,13 +157,17 @@ export default function GlobalInterceptor($rootScope, $q, $injector) { | ||
156 | unhandled = true; | 157 | unhandled = true; |
157 | } | 158 | } |
158 | } else if (rejection.status === 403) { | 159 | } else if (rejection.status === 403) { |
159 | - $rootScope.$broadcast('forbidden'); | 160 | + if (!ignoreErrors) { |
161 | + $rootScope.$broadcast('forbidden'); | ||
162 | + } | ||
160 | } else if (rejection.status === 0 || rejection.status === -1) { | 163 | } else if (rejection.status === 0 || rejection.status === -1) { |
161 | getToast().showError(getTranslate().instant('error.unable-to-connect')); | 164 | getToast().showError(getTranslate().instant('error.unable-to-connect')); |
162 | } else if (!rejection.config.url.startsWith('/api/plugins/rpc')) { | 165 | } else if (!rejection.config.url.startsWith('/api/plugins/rpc')) { |
163 | if (rejection.status === 404) { | 166 | if (rejection.status === 404) { |
164 | - getToast().showError(rejection.config.method + ": " + rejection.config.url + "<br/>" + | ||
165 | - rejection.status + ": " + rejection.statusText); | 167 | + if (!ignoreErrors) { |
168 | + getToast().showError(rejection.config.method + ": " + rejection.config.url + "<br/>" + | ||
169 | + rejection.status + ": " + rejection.statusText); | ||
170 | + } | ||
166 | } else { | 171 | } else { |
167 | unhandled = true; | 172 | unhandled = true; |
168 | } | 173 | } |
1 | +/* | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import './import-dialog.scss'; | ||
18 | + | ||
19 | +/*@ngInject*/ | ||
20 | +export default function ImportDialogController($scope, $mdDialog, toast, importTitle, importFileLabel) { | ||
21 | + | ||
22 | + var vm = this; | ||
23 | + | ||
24 | + vm.cancel = cancel; | ||
25 | + vm.importFromJson = importFromJson; | ||
26 | + vm.fileAdded = fileAdded; | ||
27 | + vm.clearFile = clearFile; | ||
28 | + | ||
29 | + vm.importTitle = importTitle; | ||
30 | + vm.importFileLabel = importFileLabel; | ||
31 | + | ||
32 | + | ||
33 | + function cancel() { | ||
34 | + $mdDialog.cancel(); | ||
35 | + } | ||
36 | + | ||
37 | + function fileAdded($file) { | ||
38 | + if ($file.getExtension() === 'json') { | ||
39 | + var reader = new FileReader(); | ||
40 | + reader.onload = function(event) { | ||
41 | + $scope.$apply(function() { | ||
42 | + if (event.target.result) { | ||
43 | + $scope.theForm.$setDirty(); | ||
44 | + var importJson = event.target.result; | ||
45 | + if (importJson && importJson.length > 0) { | ||
46 | + try { | ||
47 | + vm.importData = angular.fromJson(importJson); | ||
48 | + vm.fileName = $file.name; | ||
49 | + } catch (err) { | ||
50 | + vm.fileName = null; | ||
51 | + toast.showError(err.message); | ||
52 | + } | ||
53 | + } | ||
54 | + } | ||
55 | + }); | ||
56 | + }; | ||
57 | + reader.readAsText($file.file); | ||
58 | + } | ||
59 | + } | ||
60 | + | ||
61 | + function clearFile() { | ||
62 | + $scope.theForm.$setDirty(); | ||
63 | + vm.fileName = null; | ||
64 | + vm.importData = null; | ||
65 | + } | ||
66 | + | ||
67 | + function importFromJson() { | ||
68 | + $scope.theForm.$setPristine(); | ||
69 | + $mdDialog.hide(vm.importData); | ||
70 | + } | ||
71 | +} |
ui/src/app/import-export/import-dialog.scss
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +$previewSize: 100px; | ||
17 | + | ||
18 | +.file-input { | ||
19 | + display: none; | ||
20 | +} | ||
21 | + | ||
22 | +.tb-container { | ||
23 | + position: relative; | ||
24 | + margin-top: 32px; | ||
25 | + padding: 10px 0; | ||
26 | +} | ||
27 | + | ||
28 | +.tb-file-select-container { | ||
29 | + position: relative; | ||
30 | + height: $previewSize; | ||
31 | + width: 100%; | ||
32 | +} | ||
33 | + | ||
34 | +.tb-file-preview { | ||
35 | + max-width: $previewSize; | ||
36 | + max-height: $previewSize; | ||
37 | + width: auto; | ||
38 | + height: auto; | ||
39 | +} | ||
40 | + | ||
41 | +.tb-flow-drop { | ||
42 | + position: relative; | ||
43 | + border: dashed 2px; | ||
44 | + height: $previewSize; | ||
45 | + vertical-align: top; | ||
46 | + padding: 0 8px; | ||
47 | + overflow: hidden; | ||
48 | + min-width: 300px; | ||
49 | + label { | ||
50 | + width: 100%; | ||
51 | + font-size: 24px; | ||
52 | + text-align: center; | ||
53 | + position: absolute; | ||
54 | + top: 50%; | ||
55 | + left: 50%; | ||
56 | + transform: translate(-50%,-50%); | ||
57 | + } | ||
58 | +} | ||
59 | + | ||
60 | +.tb-file-clear-container { | ||
61 | + width: 48px; | ||
62 | + height: $previewSize; | ||
63 | + position: relative; | ||
64 | + float: right; | ||
65 | +} | ||
66 | +.tb-file-clear-btn { | ||
67 | + position: absolute !important; | ||
68 | + top: 50%; | ||
69 | + transform: translate(0%,-50%) !important; | ||
70 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-dialog aria-label="{{ vm.importTitle | translate }}"> | ||
19 | + <form name="theForm" ng-submit="vm.importFromJson()"> | ||
20 | + <md-toolbar> | ||
21 | + <div class="md-toolbar-tools"> | ||
22 | + <h2 translate>{{ vm.importTitle }}</h2> | ||
23 | + <span flex></span> | ||
24 | + <md-button class="md-icon-button" ng-click="vm.cancel()"> | ||
25 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | ||
26 | + </md-button> | ||
27 | + </div> | ||
28 | + </md-toolbar> | ||
29 | + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear> | ||
30 | + <span style="min-height: 5px;" flex="" ng-show="!loading"></span> | ||
31 | + <md-dialog-content> | ||
32 | + <div class="md-dialog-content"> | ||
33 | + <fieldset ng-disabled="loading"> | ||
34 | + <div layout="column" layout-padding> | ||
35 | + <div class="tb-container"> | ||
36 | + <label class="tb-label" translate>{{ vm.importFileLabel }}</label> | ||
37 | + <div flow-init="{singleFile:true}" | ||
38 | + flow-file-added="vm.fileAdded( $file )" class="tb-file-select-container"> | ||
39 | + <div class="tb-file-clear-container"> | ||
40 | + <md-button ng-click="vm.clearFile()" | ||
41 | + class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> | ||
42 | + <md-tooltip md-direction="top"> | ||
43 | + {{ 'action.remove' | translate }} | ||
44 | + </md-tooltip> | ||
45 | + <md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons"> | ||
46 | + close | ||
47 | + </md-icon> | ||
48 | + </md-button> | ||
49 | + </div> | ||
50 | + <div class="alert tb-flow-drop" flow-drop> | ||
51 | + <label for="select" translate>import.drop-file</label> | ||
52 | + <input class="file-input" flow-btn flow-attrs="{accept:'.json,application/json'}" id="select"> | ||
53 | + </div> | ||
54 | + </div> | ||
55 | + </div> | ||
56 | + <div> | ||
57 | + <div ng-show="!vm.fileName" translate>import.no-file</div> | ||
58 | + <div ng-show="vm.fileName">{{ vm.fileName }}</div> | ||
59 | + </div> | ||
60 | + </div> | ||
61 | + </fieldset> | ||
62 | + </div> | ||
63 | + </md-dialog-content> | ||
64 | + <md-dialog-actions layout="row"> | ||
65 | + <span flex></span> | ||
66 | + <md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid || !vm.importData" type="submit" class="md-raised md-primary"> | ||
67 | + {{ 'action.import' | translate }} | ||
68 | + </md-button> | ||
69 | + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> | ||
70 | + </md-dialog-actions> | ||
71 | + </form> | ||
72 | +</md-dialog> |
1 | +/* | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* eslint-disable import/no-unresolved, import/default */ | ||
18 | + | ||
19 | +import importDialogTemplate from './import-dialog.tpl.html'; | ||
20 | +import deviceAliasesTemplate from '../dashboard/device-aliases.tpl.html'; | ||
21 | + | ||
22 | +/* eslint-enable import/no-unresolved, import/default */ | ||
23 | + | ||
24 | + | ||
25 | +/* eslint-disable no-undef, angular/window-service, angular/document-service */ | ||
26 | + | ||
27 | +/*@ngInject*/ | ||
28 | +export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, deviceService, dashboardService, toast) { | ||
29 | + | ||
30 | + | ||
31 | + var service = { | ||
32 | + exportDashboard: exportDashboard, | ||
33 | + importDashboard: importDashboard, | ||
34 | + exportWidget: exportWidget, | ||
35 | + importWidget: importWidget | ||
36 | + } | ||
37 | + | ||
38 | + return service; | ||
39 | + | ||
40 | + // Widget functions | ||
41 | + | ||
42 | + function exportWidget(dashboard, widget) { | ||
43 | + var widgetItem = itembuffer.prepareWidgetItem(dashboard, widget); | ||
44 | + var name = widgetItem.widget.config.title; | ||
45 | + name = name.toLowerCase().replace(/\W/g,"_"); | ||
46 | + exportToPc(prepareExport(widgetItem), name + '.json'); | ||
47 | + } | ||
48 | + | ||
49 | + function importWidget($event, dashboard) { | ||
50 | + openImportDialog($event, 'dashboard.import-widget', 'dashboard.widget-file').then( | ||
51 | + function success(widgetItem) { | ||
52 | + if (!validateImportedWidget(widgetItem)) { | ||
53 | + toast.showError($translate.instant('dashboard.invalid-widget-file-error')); | ||
54 | + } else { | ||
55 | + var widget = widgetItem.widget; | ||
56 | + var aliasesInfo = widgetItem.aliasesInfo; | ||
57 | + var originalColumns = widgetItem.originalColumns; | ||
58 | + | ||
59 | + var datasourceAliases = aliasesInfo.datasourceAliases; | ||
60 | + var targetDeviceAliases = aliasesInfo.targetDeviceAliases; | ||
61 | + if (datasourceAliases || targetDeviceAliases) { | ||
62 | + var deviceAliases = {}; | ||
63 | + var datasourceAliasesMap = {}; | ||
64 | + var targetDeviceAliasesMap = {}; | ||
65 | + var aliasId = 1; | ||
66 | + var datasourceIndex; | ||
67 | + if (datasourceAliases) { | ||
68 | + for (datasourceIndex in datasourceAliases) { | ||
69 | + datasourceAliasesMap[aliasId] = datasourceIndex; | ||
70 | + deviceAliases[aliasId] = { | ||
71 | + alias: datasourceAliases[datasourceIndex].aliasName, | ||
72 | + deviceId: datasourceAliases[datasourceIndex].deviceId | ||
73 | + }; | ||
74 | + aliasId++; | ||
75 | + } | ||
76 | + } | ||
77 | + if (targetDeviceAliases) { | ||
78 | + for (datasourceIndex in targetDeviceAliases) { | ||
79 | + targetDeviceAliasesMap[aliasId] = datasourceIndex; | ||
80 | + deviceAliases[aliasId] = { | ||
81 | + alias: targetDeviceAliases[datasourceIndex].aliasName, | ||
82 | + deviceId: targetDeviceAliases[datasourceIndex].deviceId | ||
83 | + }; | ||
84 | + aliasId++; | ||
85 | + } | ||
86 | + } | ||
87 | + | ||
88 | + var aliasIds = Object.keys(deviceAliases); | ||
89 | + if (aliasIds.length > 0) { | ||
90 | + processDeviceAliases(deviceAliases, aliasIds).then( | ||
91 | + function(missingDeviceAliases) { | ||
92 | + if (Object.keys(missingDeviceAliases).length > 0) { | ||
93 | + editMissingAliases($event, [ widget ], | ||
94 | + true, 'dashboard.widget-import-missing-aliases-title', missingDeviceAliases).then( | ||
95 | + function success(updatedDeviceAliases) { | ||
96 | + for (var aliasId in updatedDeviceAliases) { | ||
97 | + var deviceAlias = updatedDeviceAliases[aliasId]; | ||
98 | + var datasourceIndex; | ||
99 | + if (datasourceAliasesMap[aliasId]) { | ||
100 | + datasourceIndex = datasourceAliasesMap[aliasId]; | ||
101 | + datasourceAliases[datasourceIndex].deviceId = deviceAlias.deviceId; | ||
102 | + } else if (targetDeviceAliasesMap[aliasId]) { | ||
103 | + datasourceIndex = targetDeviceAliasesMap[aliasId]; | ||
104 | + targetDeviceAliases[datasourceIndex].deviceId = deviceAlias.deviceId; | ||
105 | + } | ||
106 | + } | ||
107 | + addImportedWidget(dashboard, widget, aliasesInfo, originalColumns); | ||
108 | + }, | ||
109 | + function fail() {} | ||
110 | + ); | ||
111 | + } else { | ||
112 | + addImportedWidget(dashboard, widget, aliasesInfo, originalColumns); | ||
113 | + } | ||
114 | + } | ||
115 | + ); | ||
116 | + } else { | ||
117 | + addImportedWidget(dashboard, widget, aliasesInfo, originalColumns); | ||
118 | + } | ||
119 | + } else { | ||
120 | + addImportedWidget(dashboard, widget, aliasesInfo, originalColumns); | ||
121 | + } | ||
122 | + } | ||
123 | + }, | ||
124 | + function fail() {} | ||
125 | + ); | ||
126 | + } | ||
127 | + | ||
128 | + function validateImportedWidget(widgetItem) { | ||
129 | + if (angular.isUndefined(widgetItem.widget) | ||
130 | + || angular.isUndefined(widgetItem.aliasesInfo) | ||
131 | + || angular.isUndefined(widgetItem.originalColumns)) { | ||
132 | + return false; | ||
133 | + } | ||
134 | + var widget = widgetItem.widget; | ||
135 | + if (angular.isUndefined(widget.isSystemType) || | ||
136 | + angular.isUndefined(widget.bundleAlias) || | ||
137 | + angular.isUndefined(widget.typeAlias) || | ||
138 | + angular.isUndefined(widget.type)) { | ||
139 | + return false; | ||
140 | + } | ||
141 | + return true; | ||
142 | + } | ||
143 | + | ||
144 | + function addImportedWidget(dashboard, widget, aliasesInfo, originalColumns) { | ||
145 | + itembuffer.addWidgetToDashboard(dashboard, widget, aliasesInfo, originalColumns, -1, -1); | ||
146 | + } | ||
147 | + | ||
148 | + // Dashboard functions | ||
149 | + | ||
150 | + function exportDashboard(dashboardId) { | ||
151 | + dashboardService.getDashboard(dashboardId).then( | ||
152 | + function success(dashboard) { | ||
153 | + var name = dashboard.title; | ||
154 | + name = name.toLowerCase().replace(/\W/g,"_"); | ||
155 | + exportToPc(prepareExport(dashboard), name + '.json'); | ||
156 | + }, | ||
157 | + function fail(rejection) { | ||
158 | + var message = rejection; | ||
159 | + if (!message) { | ||
160 | + message = $translate.instant('error.unknown-error'); | ||
161 | + } | ||
162 | + toast.showError($translate.instant('dashboard.export-failed-error', {error: message})); | ||
163 | + } | ||
164 | + ); | ||
165 | + } | ||
166 | + | ||
167 | + function importDashboard($event) { | ||
168 | + var deferred = $q.defer(); | ||
169 | + openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then( | ||
170 | + function success(dashboard) { | ||
171 | + if (!validateImportedDashboard(dashboard)) { | ||
172 | + toast.showError($translate.instant('dashboard.invalid-dashboard-file-error')); | ||
173 | + deferred.reject(); | ||
174 | + } else { | ||
175 | + var deviceAliases = dashboard.configuration.deviceAliases; | ||
176 | + if (deviceAliases) { | ||
177 | + var aliasIds = Object.keys( deviceAliases ); | ||
178 | + if (aliasIds.length > 0) { | ||
179 | + processDeviceAliases(deviceAliases, aliasIds).then( | ||
180 | + function(missingDeviceAliases) { | ||
181 | + if (Object.keys( missingDeviceAliases ).length > 0) { | ||
182 | + editMissingAliases($event, dashboard.configuration.widgets, | ||
183 | + false, 'dashboard.dashboard-import-missing-aliases-title', missingDeviceAliases).then( | ||
184 | + function success(updatedDeviceAliases) { | ||
185 | + for (var aliasId in updatedDeviceAliases) { | ||
186 | + deviceAliases[aliasId] = updatedDeviceAliases[aliasId]; | ||
187 | + } | ||
188 | + saveImportedDashboard(dashboard, deferred); | ||
189 | + }, | ||
190 | + function fail() { | ||
191 | + deferred.reject(); | ||
192 | + } | ||
193 | + ); | ||
194 | + } else { | ||
195 | + saveImportedDashboard(dashboard, deferred); | ||
196 | + } | ||
197 | + } | ||
198 | + ) | ||
199 | + } else { | ||
200 | + saveImportedDashboard(dashboard, deferred); | ||
201 | + } | ||
202 | + } else { | ||
203 | + saveImportedDashboard(dashboard, deferred); | ||
204 | + } | ||
205 | + } | ||
206 | + }, | ||
207 | + function fail() { | ||
208 | + deferred.reject(); | ||
209 | + } | ||
210 | + ); | ||
211 | + return deferred.promise; | ||
212 | + } | ||
213 | + | ||
214 | + function saveImportedDashboard(dashboard, deferred) { | ||
215 | + dashboardService.saveDashboard(dashboard).then( | ||
216 | + function success() { | ||
217 | + deferred.resolve(); | ||
218 | + }, | ||
219 | + function fail() { | ||
220 | + deferred.reject(); | ||
221 | + } | ||
222 | + ) | ||
223 | + } | ||
224 | + | ||
225 | + function validateImportedDashboard(dashboard) { | ||
226 | + if (angular.isUndefined(dashboard.title) || angular.isUndefined(dashboard.configuration)) { | ||
227 | + return false; | ||
228 | + } | ||
229 | + return true; | ||
230 | + } | ||
231 | + | ||
232 | + function processDeviceAliases(deviceAliases, aliasIds) { | ||
233 | + var deferred = $q.defer(); | ||
234 | + var missingDeviceAliases = {}; | ||
235 | + var index = -1; | ||
236 | + checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred); | ||
237 | + return deferred.promise; | ||
238 | + } | ||
239 | + | ||
240 | + function checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred) { | ||
241 | + index++; | ||
242 | + if (index == aliasIds.length) { | ||
243 | + deferred.resolve(missingDeviceAliases); | ||
244 | + } else { | ||
245 | + checkDeviceAlias(index, aliasIds, deviceAliases, missingDeviceAliases, deferred); | ||
246 | + } | ||
247 | + } | ||
248 | + | ||
249 | + function checkDeviceAlias(index, aliasIds, deviceAliases, missingDeviceAliases, deferred) { | ||
250 | + var aliasId = aliasIds[index]; | ||
251 | + var deviceAlias = deviceAliases[aliasId]; | ||
252 | + if (deviceAlias.deviceId) { | ||
253 | + deviceService.getDevice(deviceAlias.deviceId, true).then( | ||
254 | + function success() { | ||
255 | + checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred); | ||
256 | + }, | ||
257 | + function fail() { | ||
258 | + var missingDeviceAlias = angular.copy(deviceAlias); | ||
259 | + missingDeviceAlias.deviceId = null; | ||
260 | + missingDeviceAliases[aliasId] = missingDeviceAlias; | ||
261 | + checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred); | ||
262 | + } | ||
263 | + ); | ||
264 | + } | ||
265 | + } | ||
266 | + | ||
267 | + function editMissingAliases($event, widgets, isSingleWidget, customTitle, missingDeviceAliases) { | ||
268 | + var deferred = $q.defer(); | ||
269 | + $mdDialog.show({ | ||
270 | + controller: 'DeviceAliasesController', | ||
271 | + controllerAs: 'vm', | ||
272 | + templateUrl: deviceAliasesTemplate, | ||
273 | + locals: { | ||
274 | + config: { | ||
275 | + deviceAliases: missingDeviceAliases, | ||
276 | + widgets: widgets, | ||
277 | + isSingleWidget: isSingleWidget, | ||
278 | + isSingleDevice: false, | ||
279 | + singleDeviceAlias: null, | ||
280 | + customTitle: customTitle, | ||
281 | + disableAdd: true | ||
282 | + } | ||
283 | + }, | ||
284 | + parent: angular.element($document[0].body), | ||
285 | + skipHide: true, | ||
286 | + fullscreen: true, | ||
287 | + targetEvent: $event | ||
288 | + }).then(function (updatedDeviceAliases) { | ||
289 | + deferred.resolve(updatedDeviceAliases); | ||
290 | + }, function () { | ||
291 | + deferred.reject(); | ||
292 | + }); | ||
293 | + return deferred.promise; | ||
294 | + } | ||
295 | + | ||
296 | + // Common functions | ||
297 | + | ||
298 | + function prepareExport(data) { | ||
299 | + var exportedData = angular.copy(data); | ||
300 | + if (angular.isDefined(exportedData.id)) { | ||
301 | + delete exportedData.id; | ||
302 | + } | ||
303 | + if (angular.isDefined(exportedData.createdTime)) { | ||
304 | + delete exportedData.createdTime; | ||
305 | + } | ||
306 | + if (angular.isDefined(exportedData.tenantId)) { | ||
307 | + delete exportedData.tenantId; | ||
308 | + } | ||
309 | + if (angular.isDefined(exportedData.customerId)) { | ||
310 | + delete exportedData.customerId; | ||
311 | + } | ||
312 | + return exportedData; | ||
313 | + } | ||
314 | + | ||
315 | + function exportToPc(data, filename) { | ||
316 | + if (!data) { | ||
317 | + $log.error('No data'); | ||
318 | + return; | ||
319 | + } | ||
320 | + | ||
321 | + if (!filename) { | ||
322 | + filename = 'download.json'; | ||
323 | + } | ||
324 | + | ||
325 | + if (angular.isObject(data)) { | ||
326 | + data = angular.toJson(data, 2); | ||
327 | + } | ||
328 | + | ||
329 | + var blob = new Blob([data], {type: 'text/json'}); | ||
330 | + | ||
331 | + // FOR IE: | ||
332 | + | ||
333 | + if (window.navigator && window.navigator.msSaveOrOpenBlob) { | ||
334 | + window.navigator.msSaveOrOpenBlob(blob, filename); | ||
335 | + } | ||
336 | + else{ | ||
337 | + var e = document.createEvent('MouseEvents'), | ||
338 | + a = document.createElement('a'); | ||
339 | + | ||
340 | + a.download = filename; | ||
341 | + a.href = window.URL.createObjectURL(blob); | ||
342 | + a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); | ||
343 | + e.initEvent('click', true, false, window, | ||
344 | + 0, 0, 0, 0, 0, false, false, false, false, 0, null); | ||
345 | + a.dispatchEvent(e); | ||
346 | + } | ||
347 | + } | ||
348 | + | ||
349 | + function openImportDialog($event, importTitle, importFileLabel) { | ||
350 | + var deferred = $q.defer(); | ||
351 | + $mdDialog.show({ | ||
352 | + controller: 'ImportDialogController', | ||
353 | + controllerAs: 'vm', | ||
354 | + templateUrl: importDialogTemplate, | ||
355 | + locals: { | ||
356 | + importTitle: importTitle, | ||
357 | + importFileLabel: importFileLabel | ||
358 | + }, | ||
359 | + parent: angular.element($document[0].body), | ||
360 | + skipHide: true, | ||
361 | + fullscreen: true, | ||
362 | + targetEvent: $event | ||
363 | + }).then(function (importData) { | ||
364 | + deferred.resolve(importData); | ||
365 | + }, function () { | ||
366 | + deferred.reject(); | ||
367 | + }); | ||
368 | + return deferred.promise; | ||
369 | + } | ||
370 | + | ||
371 | +} | ||
372 | + | ||
373 | +/* eslint-enable no-undef, angular/window-service, angular/document-service */ |
ui/src/app/import-export/index.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import ImportExport from './import-export.service'; | ||
18 | +import ImportDialogController from './import-dialog.controller'; | ||
19 | + | ||
20 | + | ||
21 | +export default angular.module('thingsboard.importexport', []) | ||
22 | + .factory('importExport', ImportExport) | ||
23 | + .controller('ImportDialogController', ImportDialogController) | ||
24 | + .name; |
@@ -25,11 +25,12 @@ export default angular.module('thingsboard.itembuffer', [angularStorage]) | @@ -25,11 +25,12 @@ export default angular.module('thingsboard.itembuffer', [angularStorage]) | ||
25 | .name; | 25 | .name; |
26 | 26 | ||
27 | /*@ngInject*/ | 27 | /*@ngInject*/ |
28 | -function ItemBuffer(bufferStore) { | 28 | +function ItemBuffer(bufferStore, types) { |
29 | 29 | ||
30 | const WIDGET_ITEM = "widget_item"; | 30 | const WIDGET_ITEM = "widget_item"; |
31 | 31 | ||
32 | var service = { | 32 | var service = { |
33 | + prepareWidgetItem: prepareWidgetItem, | ||
33 | copyWidget: copyWidget, | 34 | copyWidget: copyWidget, |
34 | hasWidget: hasWidget, | 35 | hasWidget: hasWidget, |
35 | pasteWidget: pasteWidget, | 36 | pasteWidget: pasteWidget, |
@@ -56,12 +57,57 @@ function ItemBuffer(bufferStore) { | @@ -56,12 +57,57 @@ function ItemBuffer(bufferStore) { | ||
56 | } | 57 | } |
57 | **/ | 58 | **/ |
58 | 59 | ||
59 | - function copyWidget(widget, aliasesInfo, originalColumns) { | ||
60 | - var widgetItem = { | 60 | + function prepareWidgetItem(dashboard, widget) { |
61 | + var aliasesInfo = { | ||
62 | + datasourceAliases: {}, | ||
63 | + targetDeviceAliases: {} | ||
64 | + }; | ||
65 | + var originalColumns = 24; | ||
66 | + if (dashboard.configuration.gridSettings && | ||
67 | + dashboard.configuration.gridSettings.columns) { | ||
68 | + originalColumns = dashboard.configuration.gridSettings.columns; | ||
69 | + } | ||
70 | + if (widget.config && dashboard.configuration | ||
71 | + && dashboard.configuration.deviceAliases) { | ||
72 | + var deviceAlias; | ||
73 | + if (widget.config.datasources) { | ||
74 | + for (var i=0;i<widget.config.datasources.length;i++) { | ||
75 | + var datasource = widget.config.datasources[i]; | ||
76 | + if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { | ||
77 | + deviceAlias = dashboard.configuration.deviceAliases[datasource.deviceAliasId]; | ||
78 | + if (deviceAlias) { | ||
79 | + aliasesInfo.datasourceAliases[i] = { | ||
80 | + aliasName: deviceAlias.alias, | ||
81 | + deviceId: deviceAlias.deviceId | ||
82 | + } | ||
83 | + } | ||
84 | + } | ||
85 | + } | ||
86 | + } | ||
87 | + if (widget.config.targetDeviceAliasIds) { | ||
88 | + for (i=0;i<widget.config.targetDeviceAliasIds.length;i++) { | ||
89 | + var targetDeviceAliasId = widget.config.targetDeviceAliasIds[i]; | ||
90 | + if (targetDeviceAliasId) { | ||
91 | + deviceAlias = dashboard.configuration.deviceAliases[targetDeviceAliasId]; | ||
92 | + if (deviceAlias) { | ||
93 | + aliasesInfo.targetDeviceAliases[i] = { | ||
94 | + aliasName: deviceAlias.alias, | ||
95 | + deviceId: deviceAlias.deviceId | ||
96 | + } | ||
97 | + } | ||
98 | + } | ||
99 | + } | ||
100 | + } | ||
101 | + } | ||
102 | + return { | ||
61 | widget: widget, | 103 | widget: widget, |
62 | aliasesInfo: aliasesInfo, | 104 | aliasesInfo: aliasesInfo, |
63 | originalColumns: originalColumns | 105 | originalColumns: originalColumns |
64 | } | 106 | } |
107 | + } | ||
108 | + | ||
109 | + function copyWidget(dashboard, widget) { | ||
110 | + var widgetItem = prepareWidgetItem(dashboard, widget); | ||
65 | bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem)); | 111 | bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem)); |
66 | } | 112 | } |
67 | 113 | ||
@@ -69,7 +115,7 @@ function ItemBuffer(bufferStore) { | @@ -69,7 +115,7 @@ function ItemBuffer(bufferStore) { | ||
69 | return bufferStore.get(WIDGET_ITEM); | 115 | return bufferStore.get(WIDGET_ITEM); |
70 | } | 116 | } |
71 | 117 | ||
72 | - function pasteWidget(targetDasgboard, position) { | 118 | + function pasteWidget(targetDashboard, position) { |
73 | var widgetItemJson = bufferStore.get(WIDGET_ITEM); | 119 | var widgetItemJson = bufferStore.get(WIDGET_ITEM); |
74 | if (widgetItemJson) { | 120 | if (widgetItemJson) { |
75 | var widgetItem = angular.fromJson(widgetItemJson); | 121 | var widgetItem = angular.fromJson(widgetItemJson); |
@@ -82,7 +128,7 @@ function ItemBuffer(bufferStore) { | @@ -82,7 +128,7 @@ function ItemBuffer(bufferStore) { | ||
82 | targetRow = position.row; | 128 | targetRow = position.row; |
83 | targetColumn = position.column; | 129 | targetColumn = position.column; |
84 | } | 130 | } |
85 | - addWidgetToDashboard(targetDasgboard, widget, aliasesInfo, originalColumns, targetRow, targetColumn); | 131 | + addWidgetToDashboard(targetDashboard, widget, aliasesInfo, originalColumns, targetRow, targetColumn); |
86 | } | 132 | } |
87 | } | 133 | } |
88 | 134 |
@@ -34,7 +34,13 @@ export default class TbAnalogueLinearGauge { | @@ -34,7 +34,13 @@ export default class TbAnalogueLinearGauge { | ||
34 | var majorTicksCount = settings.majorTicksCount || 10; | 34 | var majorTicksCount = settings.majorTicksCount || 10; |
35 | var total = maxValue-minValue; | 35 | var total = maxValue-minValue; |
36 | var step = (total/majorTicksCount); | 36 | var step = (total/majorTicksCount); |
37 | - step = parseFloat(parseFloat(step).toPrecision(12)); | 37 | + |
38 | + var valueInt = settings.valueInt || 3; | ||
39 | + | ||
40 | + var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null) | ||
41 | + ? settings.valueDec : 2; | ||
42 | + | ||
43 | + step = parseFloat(parseFloat(step).toFixed(valueDec)); | ||
38 | 44 | ||
39 | var majorTicks = []; | 45 | var majorTicks = []; |
40 | var highlights = []; | 46 | var highlights = []; |
@@ -44,7 +50,7 @@ export default class TbAnalogueLinearGauge { | @@ -44,7 +50,7 @@ export default class TbAnalogueLinearGauge { | ||
44 | var majorTick = tick + minValue; | 50 | var majorTick = tick + minValue; |
45 | majorTicks.push(majorTick); | 51 | majorTicks.push(majorTick); |
46 | var nextTick = tick+step; | 52 | var nextTick = tick+step; |
47 | - nextTick = parseFloat(parseFloat(nextTick).toPrecision(12)); | 53 | + nextTick = parseFloat(parseFloat(nextTick).toFixed(valueDec)); |
48 | if (tick<total) { | 54 | if (tick<total) { |
49 | var highlightColor = tinycolor(keyColor); | 55 | var highlightColor = tinycolor(keyColor); |
50 | var percent = tick/total; | 56 | var percent = tick/total; |
@@ -89,9 +95,8 @@ export default class TbAnalogueLinearGauge { | @@ -89,9 +95,8 @@ export default class TbAnalogueLinearGauge { | ||
89 | // borders | 95 | // borders |
90 | 96 | ||
91 | // number formats | 97 | // number formats |
92 | - valueInt: settings.valueInt || 3, | ||
93 | - valueDec: (angular.isDefined(settings.valueDec) && settings.valueDec !== null) | ||
94 | - ? settings.valueDec : 2, | 98 | + valueInt: valueInt, |
99 | + valueDec: valueDec, | ||
95 | majorTicksInt: 1, | 100 | majorTicksInt: 1, |
96 | majorTicksDec: 0, | 101 | majorTicksDec: 0, |
97 | 102 |
@@ -35,7 +35,13 @@ export default class TbAnalogueRadialGauge { | @@ -35,7 +35,13 @@ export default class TbAnalogueRadialGauge { | ||
35 | var majorTicksCount = settings.majorTicksCount || 10; | 35 | var majorTicksCount = settings.majorTicksCount || 10; |
36 | var total = maxValue-minValue; | 36 | var total = maxValue-minValue; |
37 | var step = (total/majorTicksCount); | 37 | var step = (total/majorTicksCount); |
38 | - step = parseFloat(parseFloat(step).toPrecision(12)); | 38 | + |
39 | + var valueInt = settings.valueInt || 3; | ||
40 | + | ||
41 | + var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null) | ||
42 | + ? settings.valueDec : 2; | ||
43 | + | ||
44 | + step = parseFloat(parseFloat(step).toFixed(valueDec)); | ||
39 | 45 | ||
40 | var majorTicks = []; | 46 | var majorTicks = []; |
41 | var highlights = []; | 47 | var highlights = []; |
@@ -44,7 +50,7 @@ export default class TbAnalogueRadialGauge { | @@ -44,7 +50,7 @@ export default class TbAnalogueRadialGauge { | ||
44 | while(tick<=maxValue) { | 50 | while(tick<=maxValue) { |
45 | majorTicks.push(tick); | 51 | majorTicks.push(tick); |
46 | var nextTick = tick+step; | 52 | var nextTick = tick+step; |
47 | - nextTick = parseFloat(parseFloat(nextTick).toPrecision(12)); | 53 | + nextTick = parseFloat(parseFloat(nextTick).toFixed(valueDec)); |
48 | if (tick<maxValue) { | 54 | if (tick<maxValue) { |
49 | var highlightColor = tinycolor(keyColor); | 55 | var highlightColor = tinycolor(keyColor); |
50 | var percent = (tick-minValue)/total; | 56 | var percent = (tick-minValue)/total; |
@@ -86,9 +92,8 @@ export default class TbAnalogueRadialGauge { | @@ -86,9 +92,8 @@ export default class TbAnalogueRadialGauge { | ||
86 | //borderShadowWidth: (settings.showBorder !== false) ? 3 : 0, | 92 | //borderShadowWidth: (settings.showBorder !== false) ? 3 : 0, |
87 | 93 | ||
88 | // number formats | 94 | // number formats |
89 | - valueInt: settings.valueInt || 3, | ||
90 | - valueDec: (angular.isDefined(settings.valueDec) && settings.valueDec !== null) | ||
91 | - ? settings.valueDec : 2, | 95 | + valueInt: valueInt, |
96 | + valueDec: valueDec, | ||
92 | majorTicksInt: 1, | 97 | majorTicksInt: 1, |
93 | majorTicksDec: 0, | 98 | majorTicksDec: 0, |
94 | 99 |
@@ -40,7 +40,9 @@ | @@ -40,7 +40,9 @@ | ||
40 | "refresh": "Refresh", | 40 | "refresh": "Refresh", |
41 | "undo": "Undo", | 41 | "undo": "Undo", |
42 | "copy": "Copy", | 42 | "copy": "Copy", |
43 | - "paste": "Paste" | 43 | + "paste": "Paste", |
44 | + "import": "Import", | ||
45 | + "export": "Export" | ||
44 | }, | 46 | }, |
45 | "admin": { | 47 | "admin": { |
46 | "general": "General", | 48 | "general": "General", |
@@ -214,7 +216,19 @@ | @@ -214,7 +216,19 @@ | ||
214 | "vertical-margin-required": "Vertical margin value is required.", | 216 | "vertical-margin-required": "Vertical margin value is required.", |
215 | "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", | 217 | "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", |
216 | "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", | 218 | "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", |
217 | - "display-title": "Display dashboard title" | 219 | + "display-title": "Display dashboard title", |
220 | + "import": "Import dashboard", | ||
221 | + "export": "Export dashboard", | ||
222 | + "export-failed-error": "Unable to export dashboard: {error}", | ||
223 | + "create-new-dashboard": "Create new dashboard", | ||
224 | + "dashboard-file": "Dashboard file", | ||
225 | + "invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.", | ||
226 | + "dashboard-import-missing-aliases-title": "Select missing devices for dashboard aliases", | ||
227 | + "create-new-widget": "Create new widget", | ||
228 | + "import-widget": "Import widget", | ||
229 | + "widget-file": "Widget file", | ||
230 | + "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.", | ||
231 | + "widget-import-missing-aliases-title": "Select missing devices used by widget" | ||
218 | }, | 232 | }, |
219 | "datakey": { | 233 | "datakey": { |
220 | "settings": "Settings", | 234 | "settings": "Settings", |
@@ -370,6 +384,10 @@ | @@ -370,6 +384,10 @@ | ||
370 | "avatar": "Avatar", | 384 | "avatar": "Avatar", |
371 | "open-user-menu": "Open user menu" | 385 | "open-user-menu": "Open user menu" |
372 | }, | 386 | }, |
387 | + "import": { | ||
388 | + "no-file": "No file selected", | ||
389 | + "drop-file": "Drop a JSON file or click to select a file to upload." | ||
390 | + }, | ||
373 | "item": { | 391 | "item": { |
374 | "selected": "Selected" | 392 | "selected": "Selected" |
375 | }, | 393 | }, |
@@ -612,7 +630,8 @@ | @@ -612,7 +630,8 @@ | ||
612 | "widget-type-load-failed-error": "Failed to load widget type!", | 630 | "widget-type-load-failed-error": "Failed to load widget type!", |
613 | "widget-template-load-failed-error": "Failed to load widget template!", | 631 | "widget-template-load-failed-error": "Failed to load widget template!", |
614 | "add": "Add Widget", | 632 | "add": "Add Widget", |
615 | - "undo": "Undo widget changes" | 633 | + "undo": "Undo widget changes", |
634 | + "export": "Export widget" | ||
616 | }, | 635 | }, |
617 | "widgets-bundle": { | 636 | "widgets-bundle": { |
618 | "current": "Current bundle", | 637 | "current": "Current bundle", |