Import/Export. Bug fixes.
This commit is contained in:
parent
f78b8e55f1
commit
9c616bd3a3
@ -54,6 +54,7 @@
|
||||
"json-schema-defaults": "^0.2.0",
|
||||
"justgage": "^1.2.2",
|
||||
"material-ui": "^0.16.1",
|
||||
"material-ui-number-input": "^5.0.16",
|
||||
"md-color-picker": "^0.2.6",
|
||||
"mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5",
|
||||
"moment": "^2.15.0",
|
||||
|
||||
@ -88,10 +88,10 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getDevice(deviceId) {
|
||||
function getDevice(deviceId, ignoreErrors) {
|
||||
var deferred = $q.defer();
|
||||
var url = '/api/device/' + deviceId;
|
||||
$http.get(url, null).then(function success(response) {
|
||||
$http.get(url, { ignoreErrors: ignoreErrors }).then(function success(response) {
|
||||
deferred.resolve(response.data);
|
||||
}, function fail(response) {
|
||||
deferred.reject(response.data);
|
||||
|
||||
@ -58,8 +58,10 @@ function Dashboard() {
|
||||
isMobile: '=',
|
||||
isMobileDisabled: '=?',
|
||||
isEditActionEnabled: '=',
|
||||
isExportActionEnabled: '=',
|
||||
isRemoveActionEnabled: '=',
|
||||
onEditWidget: '&?',
|
||||
onExportWidget: '&?',
|
||||
onRemoveWidget: '&?',
|
||||
onWidgetMouseDown: '&?',
|
||||
onWidgetClicked: '&?',
|
||||
@ -139,6 +141,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
|
||||
vm.showWidgetTitle = showWidgetTitle;
|
||||
vm.hasTimewindow = hasTimewindow;
|
||||
vm.editWidget = editWidget;
|
||||
vm.exportWidget = exportWidget;
|
||||
vm.removeWidget = removeWidget;
|
||||
vm.loading = loading;
|
||||
|
||||
@ -413,6 +416,16 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
|
||||
}
|
||||
}
|
||||
|
||||
function exportWidget ($event, widget) {
|
||||
resetWidgetClick();
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
if (vm.isExportActionEnabled && vm.onExportWidget) {
|
||||
vm.onExportWidget({event: $event, widget: widget});
|
||||
}
|
||||
}
|
||||
|
||||
function removeWidget($event, widget) {
|
||||
resetWidgetClick();
|
||||
if ($event) {
|
||||
|
||||
@ -62,6 +62,18 @@
|
||||
edit
|
||||
</md-icon>
|
||||
</md-button>
|
||||
<md-button ng-show="vm.isExportActionEnabled && !vm.isWidgetExpanded"
|
||||
ng-disabled="vm.loading()"
|
||||
class="md-icon-button md-primary"
|
||||
ng-click="vm.exportWidget($event, widget)"
|
||||
aria-label="{{ 'widget.export' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'widget.export' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon class="material-icons">
|
||||
file_download
|
||||
</md-icon>
|
||||
</md-button>
|
||||
<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
|
||||
ng-disabled="vm.loading()"
|
||||
class="md-icon-button md-primary"
|
||||
|
||||
@ -327,6 +327,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
|
||||
icon: "add"
|
||||
};
|
||||
|
||||
vm.addItemActionsOpen = false;
|
||||
|
||||
vm.addItemActions = vm.config.addItemActions || [];
|
||||
|
||||
vm.onGridInited = vm.config.onGridInited || function () {
|
||||
};
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@
|
||||
</tb-details-sidenav>
|
||||
</section>
|
||||
|
||||
<section layout="row" layout-wrap class="tb-footer-buttons md-fab ">
|
||||
<section layout="row" layout-wrap class="tb-footer-buttons md-fab " layout-align="start end">
|
||||
<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"
|
||||
ng-click="groupAction.onAction($event, vm.items)" aria-label="{{ groupAction.name() }}">
|
||||
<md-tooltip md-direction="top">
|
||||
@ -93,10 +93,29 @@
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="arrow_drop_up"></ng-md-icon>
|
||||
</md-button>
|
||||
<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() }}" >
|
||||
<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() }}" >
|
||||
<md-tooltip md-direction="top">
|
||||
{{ vm.addItemAction.details() }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="{{ vm.addItemAction.icon }}"></ng-md-icon>
|
||||
</md-button>
|
||||
<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()">
|
||||
<md-fab-trigger>
|
||||
<md-button ng-disabled="loading" class="tb-btn-footer md-accent md-hue-2 md-fab" aria-label="{{ vm.addItemAction.name() }}" >
|
||||
<md-tooltip md-direction="top">
|
||||
{{ vm.addItemAction.details() }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="{{ vm.addItemAction.icon }}"></ng-md-icon>
|
||||
</md-button>
|
||||
</md-fab-trigger>
|
||||
<md-fab-actions>
|
||||
<md-button ng-disabled="loading" class="md-accent md-hue-2 md-fab" ng-repeat="addItemAction in vm.addItemActions"
|
||||
ng-click="addItemAction.onAction($event)" aria-label="{{ addItemAction.name() }}" >
|
||||
<md-tooltip md-direction="top">
|
||||
{{ addItemAction.details() }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="{{addItemAction.icon}}"></ng-md-icon>
|
||||
</md-button>
|
||||
</md-fab-actions>
|
||||
</md-fab-speed-dial>
|
||||
</section>
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import ThingsboardBaseComponent from './json-form-base-component.jsx';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import NumberInput from 'material-ui-number-input';
|
||||
|
||||
class ThingsboardNumber extends React.Component {
|
||||
|
||||
@ -63,16 +63,18 @@ class ThingsboardNumber extends React.Component {
|
||||
if (this.state.focused) {
|
||||
fieldClass += " tb-focused";
|
||||
}
|
||||
var value = this.state.lastSuccessfulValue;
|
||||
value = Number(value);
|
||||
|
||||
return (
|
||||
<TextField
|
||||
<NumberInput
|
||||
className={fieldClass}
|
||||
type={this.props.form.type}
|
||||
strategy="allow"
|
||||
floatingLabelText={this.props.form.title}
|
||||
hintText={this.props.form.placeholder}
|
||||
errorText={this.props.error}
|
||||
onChange={this.preValidationCheck}
|
||||
defaultValue={this.state.lastSuccessfulValue}
|
||||
defaultValue={value}
|
||||
ref="numberField"
|
||||
disabled={this.props.form.readonly}
|
||||
onFocus={this.onFocus}
|
||||
|
||||
@ -102,10 +102,12 @@ export default function AddWidgetController($scope, widgetService, deviceService
|
||||
controllerAs: 'vm',
|
||||
templateUrl: deviceAliasesTemplate,
|
||||
locals: {
|
||||
deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases),
|
||||
aliasToWidgetsMap: null,
|
||||
isSingleDevice: true,
|
||||
singleDeviceAlias: singleDeviceAlias
|
||||
config: {
|
||||
deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases),
|
||||
widgets: null,
|
||||
isSingleDevice: true,
|
||||
singleDeviceAlias: singleDeviceAlias
|
||||
}
|
||||
},
|
||||
parent: angular.element($document[0].body),
|
||||
fullscreen: true,
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
-->
|
||||
<md-button ng-click="onAssignToCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>
|
||||
<md-button ng-click="onUnassignFromCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'customer'" class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
|
||||
<md-button ng-click="onExportDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
|
||||
<md-button ng-click="onDeleteDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
|
||||
|
||||
<md-content class="md-padding" layout="column">
|
||||
|
||||
@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html';
|
||||
|
||||
/*@ngInject*/
|
||||
export default function DashboardController(types, widgetService, userService,
|
||||
dashboardService, itembuffer, hotkeys, $window, $rootScope,
|
||||
dashboardService, itembuffer, importExport, hotkeys, $window, $rootScope,
|
||||
$scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
|
||||
|
||||
var user = userService.getCurrentUser();
|
||||
@ -53,6 +53,8 @@ export default function DashboardController(types, widgetService, userService,
|
||||
vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
|
||||
vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
|
||||
vm.editWidget = editWidget;
|
||||
vm.exportWidget = exportWidget;
|
||||
vm.importWidget = importWidget;
|
||||
vm.isTenantAdmin = isTenantAdmin;
|
||||
vm.loadDashboard = loadDashboard;
|
||||
vm.noData = noData;
|
||||
@ -210,44 +212,17 @@ export default function DashboardController(types, widgetService, userService,
|
||||
}
|
||||
|
||||
function openDeviceAliases($event) {
|
||||
var aliasToWidgetsMap = {};
|
||||
var widgetsTitleList;
|
||||
for (var w in vm.widgets) {
|
||||
var widget = vm.widgets[w];
|
||||
if (widget.type === types.widgetType.rpc.value) {
|
||||
if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
|
||||
var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
|
||||
widgetsTitleList = aliasToWidgetsMap[targetDeviceAliasId];
|
||||
if (!widgetsTitleList) {
|
||||
widgetsTitleList = [];
|
||||
aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList;
|
||||
}
|
||||
widgetsTitleList.push(widget.config.title);
|
||||
}
|
||||
} else {
|
||||
for (var i in widget.config.datasources) {
|
||||
var datasource = widget.config.datasources[i];
|
||||
if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
|
||||
widgetsTitleList = aliasToWidgetsMap[datasource.deviceAliasId];
|
||||
if (!widgetsTitleList) {
|
||||
widgetsTitleList = [];
|
||||
aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList;
|
||||
}
|
||||
widgetsTitleList.push(widget.config.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$mdDialog.show({
|
||||
controller: 'DeviceAliasesController',
|
||||
controllerAs: 'vm',
|
||||
templateUrl: deviceAliasesTemplate,
|
||||
locals: {
|
||||
deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases),
|
||||
aliasToWidgetsMap: aliasToWidgetsMap,
|
||||
isSingleDevice: false,
|
||||
singleDeviceAlias: null
|
||||
config: {
|
||||
deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases),
|
||||
widgets: vm.widgets,
|
||||
isSingleDevice: false,
|
||||
singleDeviceAlias: null
|
||||
}
|
||||
},
|
||||
parent: angular.element($document[0].body),
|
||||
skipHide: true,
|
||||
@ -300,6 +275,16 @@ export default function DashboardController(types, widgetService, userService,
|
||||
}
|
||||
}
|
||||
|
||||
function exportWidget($event, widget) {
|
||||
$event.stopPropagation();
|
||||
importExport.exportWidget(vm.dashboard, widget);
|
||||
}
|
||||
|
||||
function importWidget($event) {
|
||||
$event.stopPropagation();
|
||||
importExport.importWidget($event, vm.dashboard);
|
||||
}
|
||||
|
||||
function widgetMouseDown($event, widget) {
|
||||
if (vm.isEdit && !vm.isEditingWidget) {
|
||||
vm.dashboardContainer.selectWidget(widget, 0);
|
||||
@ -438,48 +423,7 @@ export default function DashboardController(types, widgetService, userService,
|
||||
}
|
||||
|
||||
function copyWidget($event, widget) {
|
||||
var aliasesInfo = {
|
||||
datasourceAliases: {},
|
||||
targetDeviceAliases: {}
|
||||
};
|
||||
var originalColumns = 24;
|
||||
if (vm.dashboard.configuration.gridSettings &&
|
||||
vm.dashboard.configuration.gridSettings.columns) {
|
||||
originalColumns = vm.dashboard.configuration.gridSettings.columns;
|
||||
}
|
||||
if (widget.config && vm.dashboard.configuration
|
||||
&& vm.dashboard.configuration.deviceAliases) {
|
||||
var deviceAlias;
|
||||
if (widget.config.datasources) {
|
||||
for (var i=0;i<widget.config.datasources.length;i++) {
|
||||
var datasource = widget.config.datasources[i];
|
||||
if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
|
||||
deviceAlias = vm.dashboard.configuration.deviceAliases[datasource.deviceAliasId];
|
||||
if (deviceAlias) {
|
||||
aliasesInfo.datasourceAliases[i] = {
|
||||
aliasName: deviceAlias.alias,
|
||||
deviceId: deviceAlias.deviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (widget.config.targetDeviceAliasIds) {
|
||||
for (i=0;i<widget.config.targetDeviceAliasIds.length;i++) {
|
||||
var targetDeviceAliasId = widget.config.targetDeviceAliasIds[i];
|
||||
if (targetDeviceAliasId) {
|
||||
deviceAlias = vm.dashboard.configuration.deviceAliases[targetDeviceAliasId];
|
||||
if (deviceAlias) {
|
||||
aliasesInfo.targetDeviceAliases[i] = {
|
||||
aliasName: deviceAlias.alias,
|
||||
deviceId: deviceAlias.deviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
itembuffer.copyWidget(widget, aliasesInfo, originalColumns);
|
||||
itembuffer.copyWidget(vm.dashboard, widget);
|
||||
}
|
||||
|
||||
function helpLinkIdForWidgetType() {
|
||||
|
||||
@ -36,6 +36,7 @@ export default function DashboardDirective($compile, $templateCache) {
|
||||
theForm: '=',
|
||||
onAssignToCustomer: '&',
|
||||
onUnassignFromCustomer: '&',
|
||||
onExportDashboard: '&',
|
||||
onDeleteDashboard: '&'
|
||||
}
|
||||
};
|
||||
|
||||
@ -80,8 +80,10 @@
|
||||
is-mobile="vm.forceDashboardMobileMode"
|
||||
is-mobile-disabled="vm.widgetEditMode"
|
||||
is-edit-action-enabled="vm.isEdit || vm.widgetEditMode"
|
||||
is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
|
||||
is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
|
||||
on-edit-widget="vm.editWidget(event, widget)"
|
||||
on-export-widget="vm.exportWidget(event, widget)"
|
||||
on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
|
||||
on-widget-clicked="vm.widgetClicked(event, widget)"
|
||||
on-widget-context-menu="vm.widgetContextMenu(event, widget)"
|
||||
@ -180,15 +182,38 @@
|
||||
</div>
|
||||
</tb-details-sidenav>
|
||||
<!-- </section> -->
|
||||
<section layout="row" layout-wrap class="tb-footer-buttons md-fab">
|
||||
<md-button ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
|
||||
class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
|
||||
aria-label="{{ 'dashboard.add-widget' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'dashboard.add-widget' | translate }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="add"></ng-md-icon>
|
||||
</md-button>
|
||||
<section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
|
||||
<md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
|
||||
md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
|
||||
<md-fab-trigger>
|
||||
<md-button ng-disabled="loading"
|
||||
class="tb-btn-footer md-accent md-hue-2 md-fab"
|
||||
aria-label="{{ 'dashboard.add-widget' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'dashboard.add-widget' | translate }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="add"></ng-md-icon>
|
||||
</md-button>
|
||||
</md-fab-trigger>
|
||||
<md-fab-actions>
|
||||
<md-button ng-disabled="loading"
|
||||
class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
|
||||
aria-label="{{ 'action.create' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'dashboard.create-new-widget' | translate }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="insert_drive_file"></ng-md-icon>
|
||||
</md-button>
|
||||
<md-button ng-disabled="loading"
|
||||
class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidget($event)"
|
||||
aria-label="{{ 'action.import' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'dashboard.import-widget' | translate }}
|
||||
</md-tooltip>
|
||||
<ng-md-icon icon="file_upload"></ng-md-icon>
|
||||
</md-button>
|
||||
</md-fab-actions>
|
||||
</md-fab-speed-dial>
|
||||
<md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading && !vm.widgetEditMode" ng-disabled="loading"
|
||||
class="tb-btn-footer md-accent md-hue-2 md-fab"
|
||||
aria-label="{{ 'action.apply' | translate }}"
|
||||
|
||||
@ -23,7 +23,7 @@ import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.ht
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function DashboardsController(userService, dashboardService, customerService, $scope, $controller, $state, $stateParams, $mdDialog, $document, $q, $translate) {
|
||||
export default function DashboardsController(userService, dashboardService, customerService, importExport, $scope, $controller, $state, $stateParams, $mdDialog, $document, $q, $translate) {
|
||||
|
||||
var customerId = $stateParams.customerId;
|
||||
|
||||
@ -86,6 +86,7 @@ export default function DashboardsController(userService, dashboardService, cust
|
||||
|
||||
vm.assignToCustomer = assignToCustomer;
|
||||
vm.unassignFromCustomer = unassignFromCustomer;
|
||||
vm.exportDashboard = exportDashboard;
|
||||
|
||||
initController();
|
||||
|
||||
@ -113,6 +114,14 @@ export default function DashboardsController(userService, dashboardService, cust
|
||||
};
|
||||
|
||||
dashboardActionsList.push(
|
||||
{
|
||||
onAction: function ($event, item) {
|
||||
exportDashboard($event, item);
|
||||
},
|
||||
name: function() { $translate.instant('action.export') },
|
||||
details: function() { return $translate.instant('dashboard.export') },
|
||||
icon: "file_download"
|
||||
},
|
||||
{
|
||||
onAction: function ($event, item) {
|
||||
assignToCustomer($event, [ item.id.id ]);
|
||||
@ -158,7 +167,27 @@ export default function DashboardsController(userService, dashboardService, cust
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
vm.dashboardGridConfig.addItemActions = [];
|
||||
vm.dashboardGridConfig.addItemActions.push({
|
||||
onAction: function ($event) {
|
||||
vm.grid.addItem($event);
|
||||
},
|
||||
name: function() { return $translate.instant('action.create') },
|
||||
details: function() { return $translate.instant('dashboard.create-new-dashboard') },
|
||||
icon: "insert_drive_file"
|
||||
});
|
||||
vm.dashboardGridConfig.addItemActions.push({
|
||||
onAction: function ($event) {
|
||||
importExport.importDashboard($event).then(
|
||||
function() {
|
||||
vm.grid.refreshList();
|
||||
}
|
||||
);
|
||||
},
|
||||
name: function() { return $translate.instant('action.import') },
|
||||
details: function() { return $translate.instant('dashboard.import') },
|
||||
icon: "file_upload"
|
||||
});
|
||||
} else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
|
||||
fetchDashboardsFunction = function (pageLink) {
|
||||
return dashboardService.getCustomerDashboards(customerId, pageLink);
|
||||
@ -344,6 +373,11 @@ export default function DashboardsController(userService, dashboardService, cust
|
||||
});
|
||||
}
|
||||
|
||||
function exportDashboard($event, dashboard) {
|
||||
$event.stopPropagation();
|
||||
importExport.exportDashboard(dashboard.id.id);
|
||||
}
|
||||
|
||||
function unassignDashboardsFromCustomer($event, items) {
|
||||
var confirm = $mdDialog.confirm()
|
||||
.targetEvent($event)
|
||||
|
||||
@ -25,5 +25,6 @@
|
||||
the-form="vm.grid.detailsForm"
|
||||
on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
|
||||
on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
|
||||
on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
|
||||
</tb-grid>
|
||||
|
||||
@ -17,16 +17,18 @@ import './device-aliases.scss';
|
||||
|
||||
/*@ngInject*/
|
||||
export default function DeviceAliasesController(deviceService, toast, $scope, $mdDialog, $document, $q, $translate,
|
||||
deviceAliases, aliasToWidgetsMap, isSingleDevice, singleDeviceAlias) {
|
||||
types, config) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.isSingleDevice = isSingleDevice;
|
||||
vm.singleDeviceAlias = singleDeviceAlias;
|
||||
vm.isSingleDevice = config.isSingleDevice;
|
||||
vm.singleDeviceAlias = config.singleDeviceAlias;
|
||||
vm.deviceAliases = [];
|
||||
vm.aliasToWidgetsMap = aliasToWidgetsMap;
|
||||
vm.singleDevice = null;
|
||||
vm.singleDeviceSearchText = '';
|
||||
vm.title = config.customTitle ? config.customTitle : 'device.aliases';
|
||||
vm.disableAdd = config.disableAdd;
|
||||
vm.aliasToWidgetsMap = {};
|
||||
|
||||
vm.addAlias = addAlias;
|
||||
vm.cancel = cancel;
|
||||
@ -39,9 +41,48 @@ export default function DeviceAliasesController(deviceService, toast, $scope, $m
|
||||
initController();
|
||||
|
||||
function initController() {
|
||||
for (var aliasId in deviceAliases) {
|
||||
var alias = deviceAliases[aliasId].alias;
|
||||
var deviceId = deviceAliases[aliasId].deviceId;
|
||||
var aliasId;
|
||||
if (config.widgets) {
|
||||
var widgetsTitleList, widget;
|
||||
if (config.isSingleWidget && config.widgets.length == 1) {
|
||||
widget = config.widgets[0];
|
||||
widgetsTitleList = [widget.config.title];
|
||||
for (aliasId in config.deviceAliases) {
|
||||
vm.aliasToWidgetsMap[aliasId] = widgetsTitleList;
|
||||
}
|
||||
} else {
|
||||
for (var w in config.widgets) {
|
||||
widget = config.widgets[w];
|
||||
if (widget.type === types.widgetType.rpc.value) {
|
||||
if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
|
||||
var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
|
||||
widgetsTitleList = vm.aliasToWidgetsMap[targetDeviceAliasId];
|
||||
if (!widgetsTitleList) {
|
||||
widgetsTitleList = [];
|
||||
vm.aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList;
|
||||
}
|
||||
widgetsTitleList.push(widget.config.title);
|
||||
}
|
||||
} else {
|
||||
for (var i in widget.config.datasources) {
|
||||
var datasource = widget.config.datasources[i];
|
||||
if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
|
||||
widgetsTitleList = vm.aliasToWidgetsMap[datasource.deviceAliasId];
|
||||
if (!widgetsTitleList) {
|
||||
widgetsTitleList = [];
|
||||
vm.aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList;
|
||||
}
|
||||
widgetsTitleList.push(widget.config.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (aliasId in config.deviceAliases) {
|
||||
var alias = config.deviceAliases[aliasId].alias;
|
||||
var deviceId = config.deviceAliases[aliasId].deviceId;
|
||||
var deviceAlias = {id: aliasId, alias: alias, device: null, changed: false, searchText: ''};
|
||||
if (deviceId) {
|
||||
fetchAliasDevice(deviceAlias, deviceId);
|
||||
|
||||
@ -15,11 +15,11 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<md-dialog style="width: 700px;" aria-label="{{ 'device.aliases' | translate }}">
|
||||
<md-dialog style="width: 700px;" aria-label="{{ vm.title | translate }}">
|
||||
<form name="theForm" ng-submit="vm.save()">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>{{ vm.isSingleDevice ? ('device.select-device-for-alias' | translate:vm.singleDeviceAlias ) : ('device.aliases' | translate) }}</h2>
|
||||
<h2>{{ vm.isSingleDevice ? ('device.select-device-for-alias' | translate:vm.singleDeviceAlias ) : (vm.title | translate) }}</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="vm.cancel()">
|
||||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
|
||||
@ -109,7 +109,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="!vm.isSingleDevice" style="padding-bottom: 10px;">
|
||||
<div ng-show="!vm.isSingleDevice && !vm.disableAdd" style="padding-bottom: 10px;">
|
||||
<md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'device.add-alias' | translate }}
|
||||
|
||||
@ -76,10 +76,12 @@ export default function EditWidgetDirective($compile, $templateCache, widgetServ
|
||||
controllerAs: 'vm',
|
||||
templateUrl: deviceAliasesTemplate,
|
||||
locals: {
|
||||
deviceAliases: angular.copy(scope.dashboard.configuration.deviceAliases),
|
||||
aliasToWidgetsMap: null,
|
||||
isSingleDevice: true,
|
||||
singleDeviceAlias: singleDeviceAlias
|
||||
config: {
|
||||
deviceAliases: angular.copy(scope.dashboard.configuration.deviceAliases),
|
||||
widgets: null,
|
||||
isSingleDevice: true,
|
||||
singleDeviceAlias: singleDeviceAlias
|
||||
}
|
||||
},
|
||||
parent: angular.element($document[0].body),
|
||||
fullscreen: true,
|
||||
|
||||
@ -30,6 +30,7 @@ import thingsboardExpandFullscreen from '../components/expand-fullscreen.directi
|
||||
import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
|
||||
import thingsboardTypes from '../common/types.constant';
|
||||
import thingsboardItemBuffer from '../services/item-buffer.service';
|
||||
import thingsboardImportExport from '../import-export';
|
||||
|
||||
import DashboardRoutes from './dashboard.routes';
|
||||
import DashboardsController from './dashboards.controller';
|
||||
@ -47,6 +48,7 @@ export default angular.module('thingsboard.dashboard', [
|
||||
gridster.name,
|
||||
thingsboardTypes,
|
||||
thingsboardItemBuffer,
|
||||
thingsboardImportExport,
|
||||
thingsboardGrid,
|
||||
thingsboardApiWidget,
|
||||
thingsboardApiUser,
|
||||
|
||||
@ -148,6 +148,7 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
|
||||
$rootScope.loading = false;
|
||||
}
|
||||
var unhandled = false;
|
||||
var ignoreErrors = rejection.config.ignoreErrors;
|
||||
if (rejection.refreshTokenPending || rejection.status === 401) {
|
||||
var errorCode = rejectionErrorCode(rejection);
|
||||
if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) {
|
||||
@ -156,13 +157,17 @@ export default function GlobalInterceptor($rootScope, $q, $injector) {
|
||||
unhandled = true;
|
||||
}
|
||||
} else if (rejection.status === 403) {
|
||||
$rootScope.$broadcast('forbidden');
|
||||
if (!ignoreErrors) {
|
||||
$rootScope.$broadcast('forbidden');
|
||||
}
|
||||
} else if (rejection.status === 0 || rejection.status === -1) {
|
||||
getToast().showError(getTranslate().instant('error.unable-to-connect'));
|
||||
} else if (!rejection.config.url.startsWith('/api/plugins/rpc')) {
|
||||
if (rejection.status === 404) {
|
||||
getToast().showError(rejection.config.method + ": " + rejection.config.url + "<br/>" +
|
||||
rejection.status + ": " + rejection.statusText);
|
||||
if (!ignoreErrors) {
|
||||
getToast().showError(rejection.config.method + ": " + rejection.config.url + "<br/>" +
|
||||
rejection.status + ": " + rejection.statusText);
|
||||
}
|
||||
} else {
|
||||
unhandled = true;
|
||||
}
|
||||
|
||||
71
ui/src/app/import-export/import-dialog.controller.js
Normal file
71
ui/src/app/import-export/import-dialog.controller.js
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright © 2016 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './import-dialog.scss';
|
||||
|
||||
/*@ngInject*/
|
||||
export default function ImportDialogController($scope, $mdDialog, toast, importTitle, importFileLabel) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.cancel = cancel;
|
||||
vm.importFromJson = importFromJson;
|
||||
vm.fileAdded = fileAdded;
|
||||
vm.clearFile = clearFile;
|
||||
|
||||
vm.importTitle = importTitle;
|
||||
vm.importFileLabel = importFileLabel;
|
||||
|
||||
|
||||
function cancel() {
|
||||
$mdDialog.cancel();
|
||||
}
|
||||
|
||||
function fileAdded($file) {
|
||||
if ($file.getExtension() === 'json') {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
$scope.$apply(function() {
|
||||
if (event.target.result) {
|
||||
$scope.theForm.$setDirty();
|
||||
var importJson = event.target.result;
|
||||
if (importJson && importJson.length > 0) {
|
||||
try {
|
||||
vm.importData = angular.fromJson(importJson);
|
||||
vm.fileName = $file.name;
|
||||
} catch (err) {
|
||||
vm.fileName = null;
|
||||
toast.showError(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
reader.readAsText($file.file);
|
||||
}
|
||||
}
|
||||
|
||||
function clearFile() {
|
||||
$scope.theForm.$setDirty();
|
||||
vm.fileName = null;
|
||||
vm.importData = null;
|
||||
}
|
||||
|
||||
function importFromJson() {
|
||||
$scope.theForm.$setPristine();
|
||||
$mdDialog.hide(vm.importData);
|
||||
}
|
||||
}
|
||||
70
ui/src/app/import-export/import-dialog.scss
Normal file
70
ui/src/app/import-export/import-dialog.scss
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright © 2016 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
$previewSize: 100px;
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tb-container {
|
||||
position: relative;
|
||||
margin-top: 32px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.tb-file-select-container {
|
||||
position: relative;
|
||||
height: $previewSize;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tb-file-preview {
|
||||
max-width: $previewSize;
|
||||
max-height: $previewSize;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tb-flow-drop {
|
||||
position: relative;
|
||||
border: dashed 2px;
|
||||
height: $previewSize;
|
||||
vertical-align: top;
|
||||
padding: 0 8px;
|
||||
overflow: hidden;
|
||||
min-width: 300px;
|
||||
label {
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.tb-file-clear-container {
|
||||
width: 48px;
|
||||
height: $previewSize;
|
||||
position: relative;
|
||||
float: right;
|
||||
}
|
||||
.tb-file-clear-btn {
|
||||
position: absolute !important;
|
||||
top: 50%;
|
||||
transform: translate(0%,-50%) !important;
|
||||
}
|
||||
72
ui/src/app/import-export/import-dialog.tpl.html
Normal file
72
ui/src/app/import-export/import-dialog.tpl.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<md-dialog aria-label="{{ vm.importTitle | translate }}">
|
||||
<form name="theForm" ng-submit="vm.importFromJson()">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2 translate>{{ vm.importTitle }}</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="vm.cancel()">
|
||||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
|
||||
<span style="min-height: 5px;" flex="" ng-show="!loading"></span>
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content">
|
||||
<fieldset ng-disabled="loading">
|
||||
<div layout="column" layout-padding>
|
||||
<div class="tb-container">
|
||||
<label class="tb-label" translate>{{ vm.importFileLabel }}</label>
|
||||
<div flow-init="{singleFile:true}"
|
||||
flow-file-added="vm.fileAdded( $file )" class="tb-file-select-container">
|
||||
<div class="tb-file-clear-container">
|
||||
<md-button ng-click="vm.clearFile()"
|
||||
class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}">
|
||||
<md-tooltip md-direction="top">
|
||||
{{ 'action.remove' | translate }}
|
||||
</md-tooltip>
|
||||
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons">
|
||||
close
|
||||
</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
<div class="alert tb-flow-drop" flow-drop>
|
||||
<label for="select" translate>import.drop-file</label>
|
||||
<input class="file-input" flow-btn flow-attrs="{accept:'.json,application/json'}" id="select">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-show="!vm.fileName" translate>import.no-file</div>
|
||||
<div ng-show="vm.fileName">{{ vm.fileName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid || !vm.importData" type="submit" class="md-raised md-primary">
|
||||
{{ 'action.import' | translate }}
|
||||
</md-button>
|
||||
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
|
||||
</md-dialog-actions>
|
||||
</form>
|
||||
</md-dialog>
|
||||
373
ui/src/app/import-export/import-export.service.js
Normal file
373
ui/src/app/import-export/import-export.service.js
Normal file
@ -0,0 +1,373 @@
|
||||
/*
|
||||
* Copyright © 2016 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable import/no-unresolved, import/default */
|
||||
|
||||
import importDialogTemplate from './import-dialog.tpl.html';
|
||||
import deviceAliasesTemplate from '../dashboard/device-aliases.tpl.html';
|
||||
|
||||
/* eslint-enable import/no-unresolved, import/default */
|
||||
|
||||
|
||||
/* eslint-disable no-undef, angular/window-service, angular/document-service */
|
||||
|
||||
/*@ngInject*/
|
||||
export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, deviceService, dashboardService, toast) {
|
||||
|
||||
|
||||
var service = {
|
||||
exportDashboard: exportDashboard,
|
||||
importDashboard: importDashboard,
|
||||
exportWidget: exportWidget,
|
||||
importWidget: importWidget
|
||||
}
|
||||
|
||||
return service;
|
||||
|
||||
// Widget functions
|
||||
|
||||
function exportWidget(dashboard, widget) {
|
||||
var widgetItem = itembuffer.prepareWidgetItem(dashboard, widget);
|
||||
var name = widgetItem.widget.config.title;
|
||||
name = name.toLowerCase().replace(/\W/g,"_");
|
||||
exportToPc(prepareExport(widgetItem), name + '.json');
|
||||
}
|
||||
|
||||
function importWidget($event, dashboard) {
|
||||
openImportDialog($event, 'dashboard.import-widget', 'dashboard.widget-file').then(
|
||||
function success(widgetItem) {
|
||||
if (!validateImportedWidget(widgetItem)) {
|
||||
toast.showError($translate.instant('dashboard.invalid-widget-file-error'));
|
||||
} else {
|
||||
var widget = widgetItem.widget;
|
||||
var aliasesInfo = widgetItem.aliasesInfo;
|
||||
var originalColumns = widgetItem.originalColumns;
|
||||
|
||||
var datasourceAliases = aliasesInfo.datasourceAliases;
|
||||
var targetDeviceAliases = aliasesInfo.targetDeviceAliases;
|
||||
if (datasourceAliases || targetDeviceAliases) {
|
||||
var deviceAliases = {};
|
||||
var datasourceAliasesMap = {};
|
||||
var targetDeviceAliasesMap = {};
|
||||
var aliasId = 1;
|
||||
var datasourceIndex;
|
||||
if (datasourceAliases) {
|
||||
for (datasourceIndex in datasourceAliases) {
|
||||
datasourceAliasesMap[aliasId] = datasourceIndex;
|
||||
deviceAliases[aliasId] = {
|
||||
alias: datasourceAliases[datasourceIndex].aliasName,
|
||||
deviceId: datasourceAliases[datasourceIndex].deviceId
|
||||
};
|
||||
aliasId++;
|
||||
}
|
||||
}
|
||||
if (targetDeviceAliases) {
|
||||
for (datasourceIndex in targetDeviceAliases) {
|
||||
targetDeviceAliasesMap[aliasId] = datasourceIndex;
|
||||
deviceAliases[aliasId] = {
|
||||
alias: targetDeviceAliases[datasourceIndex].aliasName,
|
||||
deviceId: targetDeviceAliases[datasourceIndex].deviceId
|
||||
};
|
||||
aliasId++;
|
||||
}
|
||||
}
|
||||
|
||||
var aliasIds = Object.keys(deviceAliases);
|
||||
if (aliasIds.length > 0) {
|
||||
processDeviceAliases(deviceAliases, aliasIds).then(
|
||||
function(missingDeviceAliases) {
|
||||
if (Object.keys(missingDeviceAliases).length > 0) {
|
||||
editMissingAliases($event, [ widget ],
|
||||
true, 'dashboard.widget-import-missing-aliases-title', missingDeviceAliases).then(
|
||||
function success(updatedDeviceAliases) {
|
||||
for (var aliasId in updatedDeviceAliases) {
|
||||
var deviceAlias = updatedDeviceAliases[aliasId];
|
||||
var datasourceIndex;
|
||||
if (datasourceAliasesMap[aliasId]) {
|
||||
datasourceIndex = datasourceAliasesMap[aliasId];
|
||||
datasourceAliases[datasourceIndex].deviceId = deviceAlias.deviceId;
|
||||
} else if (targetDeviceAliasesMap[aliasId]) {
|
||||
datasourceIndex = targetDeviceAliasesMap[aliasId];
|
||||
targetDeviceAliases[datasourceIndex].deviceId = deviceAlias.deviceId;
|
||||
}
|
||||
}
|
||||
addImportedWidget(dashboard, widget, aliasesInfo, originalColumns);
|
||||
},
|
||||
function fail() {}
|
||||
);
|
||||
} else {
|
||||
addImportedWidget(dashboard, widget, aliasesInfo, originalColumns);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
addImportedWidget(dashboard, widget, aliasesInfo, originalColumns);
|
||||
}
|
||||
} else {
|
||||
addImportedWidget(dashboard, widget, aliasesInfo, originalColumns);
|
||||
}
|
||||
}
|
||||
},
|
||||
function fail() {}
|
||||
);
|
||||
}
|
||||
|
||||
function validateImportedWidget(widgetItem) {
|
||||
if (angular.isUndefined(widgetItem.widget)
|
||||
|| angular.isUndefined(widgetItem.aliasesInfo)
|
||||
|| angular.isUndefined(widgetItem.originalColumns)) {
|
||||
return false;
|
||||
}
|
||||
var widget = widgetItem.widget;
|
||||
if (angular.isUndefined(widget.isSystemType) ||
|
||||
angular.isUndefined(widget.bundleAlias) ||
|
||||
angular.isUndefined(widget.typeAlias) ||
|
||||
angular.isUndefined(widget.type)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function addImportedWidget(dashboard, widget, aliasesInfo, originalColumns) {
|
||||
itembuffer.addWidgetToDashboard(dashboard, widget, aliasesInfo, originalColumns, -1, -1);
|
||||
}
|
||||
|
||||
// Dashboard functions
|
||||
|
||||
function exportDashboard(dashboardId) {
|
||||
dashboardService.getDashboard(dashboardId).then(
|
||||
function success(dashboard) {
|
||||
var name = dashboard.title;
|
||||
name = name.toLowerCase().replace(/\W/g,"_");
|
||||
exportToPc(prepareExport(dashboard), name + '.json');
|
||||
},
|
||||
function fail(rejection) {
|
||||
var message = rejection;
|
||||
if (!message) {
|
||||
message = $translate.instant('error.unknown-error');
|
||||
}
|
||||
toast.showError($translate.instant('dashboard.export-failed-error', {error: message}));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function importDashboard($event) {
|
||||
var deferred = $q.defer();
|
||||
openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(
|
||||
function success(dashboard) {
|
||||
if (!validateImportedDashboard(dashboard)) {
|
||||
toast.showError($translate.instant('dashboard.invalid-dashboard-file-error'));
|
||||
deferred.reject();
|
||||
} else {
|
||||
var deviceAliases = dashboard.configuration.deviceAliases;
|
||||
if (deviceAliases) {
|
||||
var aliasIds = Object.keys( deviceAliases );
|
||||
if (aliasIds.length > 0) {
|
||||
processDeviceAliases(deviceAliases, aliasIds).then(
|
||||
function(missingDeviceAliases) {
|
||||
if (Object.keys( missingDeviceAliases ).length > 0) {
|
||||
editMissingAliases($event, dashboard.configuration.widgets,
|
||||
false, 'dashboard.dashboard-import-missing-aliases-title', missingDeviceAliases).then(
|
||||
function success(updatedDeviceAliases) {
|
||||
for (var aliasId in updatedDeviceAliases) {
|
||||
deviceAliases[aliasId] = updatedDeviceAliases[aliasId];
|
||||
}
|
||||
saveImportedDashboard(dashboard, deferred);
|
||||
},
|
||||
function fail() {
|
||||
deferred.reject();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
saveImportedDashboard(dashboard, deferred);
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
saveImportedDashboard(dashboard, deferred);
|
||||
}
|
||||
} else {
|
||||
saveImportedDashboard(dashboard, deferred);
|
||||
}
|
||||
}
|
||||
},
|
||||
function fail() {
|
||||
deferred.reject();
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function saveImportedDashboard(dashboard, deferred) {
|
||||
dashboardService.saveDashboard(dashboard).then(
|
||||
function success() {
|
||||
deferred.resolve();
|
||||
},
|
||||
function fail() {
|
||||
deferred.reject();
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function validateImportedDashboard(dashboard) {
|
||||
if (angular.isUndefined(dashboard.title) || angular.isUndefined(dashboard.configuration)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function processDeviceAliases(deviceAliases, aliasIds) {
|
||||
var deferred = $q.defer();
|
||||
var missingDeviceAliases = {};
|
||||
var index = -1;
|
||||
checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred) {
|
||||
index++;
|
||||
if (index == aliasIds.length) {
|
||||
deferred.resolve(missingDeviceAliases);
|
||||
} else {
|
||||
checkDeviceAlias(index, aliasIds, deviceAliases, missingDeviceAliases, deferred);
|
||||
}
|
||||
}
|
||||
|
||||
function checkDeviceAlias(index, aliasIds, deviceAliases, missingDeviceAliases, deferred) {
|
||||
var aliasId = aliasIds[index];
|
||||
var deviceAlias = deviceAliases[aliasId];
|
||||
if (deviceAlias.deviceId) {
|
||||
deviceService.getDevice(deviceAlias.deviceId, true).then(
|
||||
function success() {
|
||||
checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred);
|
||||
},
|
||||
function fail() {
|
||||
var missingDeviceAlias = angular.copy(deviceAlias);
|
||||
missingDeviceAlias.deviceId = null;
|
||||
missingDeviceAliases[aliasId] = missingDeviceAlias;
|
||||
checkNextDeviceAliasOrComplete(index, aliasIds, deviceAliases, missingDeviceAliases, deferred);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function editMissingAliases($event, widgets, isSingleWidget, customTitle, missingDeviceAliases) {
|
||||
var deferred = $q.defer();
|
||||
$mdDialog.show({
|
||||
controller: 'DeviceAliasesController',
|
||||
controllerAs: 'vm',
|
||||
templateUrl: deviceAliasesTemplate,
|
||||
locals: {
|
||||
config: {
|
||||
deviceAliases: missingDeviceAliases,
|
||||
widgets: widgets,
|
||||
isSingleWidget: isSingleWidget,
|
||||
isSingleDevice: false,
|
||||
singleDeviceAlias: null,
|
||||
customTitle: customTitle,
|
||||
disableAdd: true
|
||||
}
|
||||
},
|
||||
parent: angular.element($document[0].body),
|
||||
skipHide: true,
|
||||
fullscreen: true,
|
||||
targetEvent: $event
|
||||
}).then(function (updatedDeviceAliases) {
|
||||
deferred.resolve(updatedDeviceAliases);
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Common functions
|
||||
|
||||
function prepareExport(data) {
|
||||
var exportedData = angular.copy(data);
|
||||
if (angular.isDefined(exportedData.id)) {
|
||||
delete exportedData.id;
|
||||
}
|
||||
if (angular.isDefined(exportedData.createdTime)) {
|
||||
delete exportedData.createdTime;
|
||||
}
|
||||
if (angular.isDefined(exportedData.tenantId)) {
|
||||
delete exportedData.tenantId;
|
||||
}
|
||||
if (angular.isDefined(exportedData.customerId)) {
|
||||
delete exportedData.customerId;
|
||||
}
|
||||
return exportedData;
|
||||
}
|
||||
|
||||
function exportToPc(data, filename) {
|
||||
if (!data) {
|
||||
$log.error('No data');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filename) {
|
||||
filename = 'download.json';
|
||||
}
|
||||
|
||||
if (angular.isObject(data)) {
|
||||
data = angular.toJson(data, 2);
|
||||
}
|
||||
|
||||
var blob = new Blob([data], {type: 'text/json'});
|
||||
|
||||
// FOR IE:
|
||||
|
||||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveOrOpenBlob(blob, filename);
|
||||
}
|
||||
else{
|
||||
var e = document.createEvent('MouseEvents'),
|
||||
a = document.createElement('a');
|
||||
|
||||
a.download = filename;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
|
||||
e.initEvent('click', true, false, window,
|
||||
0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
a.dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
function openImportDialog($event, importTitle, importFileLabel) {
|
||||
var deferred = $q.defer();
|
||||
$mdDialog.show({
|
||||
controller: 'ImportDialogController',
|
||||
controllerAs: 'vm',
|
||||
templateUrl: importDialogTemplate,
|
||||
locals: {
|
||||
importTitle: importTitle,
|
||||
importFileLabel: importFileLabel
|
||||
},
|
||||
parent: angular.element($document[0].body),
|
||||
skipHide: true,
|
||||
fullscreen: true,
|
||||
targetEvent: $event
|
||||
}).then(function (importData) {
|
||||
deferred.resolve(importData);
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* eslint-enable no-undef, angular/window-service, angular/document-service */
|
||||
24
ui/src/app/import-export/index.js
Normal file
24
ui/src/app/import-export/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright © 2016 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import ImportExport from './import-export.service';
|
||||
import ImportDialogController from './import-dialog.controller';
|
||||
|
||||
|
||||
export default angular.module('thingsboard.importexport', [])
|
||||
.factory('importExport', ImportExport)
|
||||
.controller('ImportDialogController', ImportDialogController)
|
||||
.name;
|
||||
@ -25,11 +25,12 @@ export default angular.module('thingsboard.itembuffer', [angularStorage])
|
||||
.name;
|
||||
|
||||
/*@ngInject*/
|
||||
function ItemBuffer(bufferStore) {
|
||||
function ItemBuffer(bufferStore, types) {
|
||||
|
||||
const WIDGET_ITEM = "widget_item";
|
||||
|
||||
var service = {
|
||||
prepareWidgetItem: prepareWidgetItem,
|
||||
copyWidget: copyWidget,
|
||||
hasWidget: hasWidget,
|
||||
pasteWidget: pasteWidget,
|
||||
@ -56,12 +57,57 @@ function ItemBuffer(bufferStore) {
|
||||
}
|
||||
**/
|
||||
|
||||
function copyWidget(widget, aliasesInfo, originalColumns) {
|
||||
var widgetItem = {
|
||||
function prepareWidgetItem(dashboard, widget) {
|
||||
var aliasesInfo = {
|
||||
datasourceAliases: {},
|
||||
targetDeviceAliases: {}
|
||||
};
|
||||
var originalColumns = 24;
|
||||
if (dashboard.configuration.gridSettings &&
|
||||
dashboard.configuration.gridSettings.columns) {
|
||||
originalColumns = dashboard.configuration.gridSettings.columns;
|
||||
}
|
||||
if (widget.config && dashboard.configuration
|
||||
&& dashboard.configuration.deviceAliases) {
|
||||
var deviceAlias;
|
||||
if (widget.config.datasources) {
|
||||
for (var i=0;i<widget.config.datasources.length;i++) {
|
||||
var datasource = widget.config.datasources[i];
|
||||
if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
|
||||
deviceAlias = dashboard.configuration.deviceAliases[datasource.deviceAliasId];
|
||||
if (deviceAlias) {
|
||||
aliasesInfo.datasourceAliases[i] = {
|
||||
aliasName: deviceAlias.alias,
|
||||
deviceId: deviceAlias.deviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (widget.config.targetDeviceAliasIds) {
|
||||
for (i=0;i<widget.config.targetDeviceAliasIds.length;i++) {
|
||||
var targetDeviceAliasId = widget.config.targetDeviceAliasIds[i];
|
||||
if (targetDeviceAliasId) {
|
||||
deviceAlias = dashboard.configuration.deviceAliases[targetDeviceAliasId];
|
||||
if (deviceAlias) {
|
||||
aliasesInfo.targetDeviceAliases[i] = {
|
||||
aliasName: deviceAlias.alias,
|
||||
deviceId: deviceAlias.deviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
widget: widget,
|
||||
aliasesInfo: aliasesInfo,
|
||||
originalColumns: originalColumns
|
||||
}
|
||||
}
|
||||
|
||||
function copyWidget(dashboard, widget) {
|
||||
var widgetItem = prepareWidgetItem(dashboard, widget);
|
||||
bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));
|
||||
}
|
||||
|
||||
@ -69,7 +115,7 @@ function ItemBuffer(bufferStore) {
|
||||
return bufferStore.get(WIDGET_ITEM);
|
||||
}
|
||||
|
||||
function pasteWidget(targetDasgboard, position) {
|
||||
function pasteWidget(targetDashboard, position) {
|
||||
var widgetItemJson = bufferStore.get(WIDGET_ITEM);
|
||||
if (widgetItemJson) {
|
||||
var widgetItem = angular.fromJson(widgetItemJson);
|
||||
@ -82,7 +128,7 @@ function ItemBuffer(bufferStore) {
|
||||
targetRow = position.row;
|
||||
targetColumn = position.column;
|
||||
}
|
||||
addWidgetToDashboard(targetDasgboard, widget, aliasesInfo, originalColumns, targetRow, targetColumn);
|
||||
addWidgetToDashboard(targetDashboard, widget, aliasesInfo, originalColumns, targetRow, targetColumn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,13 @@ export default class TbAnalogueLinearGauge {
|
||||
var majorTicksCount = settings.majorTicksCount || 10;
|
||||
var total = maxValue-minValue;
|
||||
var step = (total/majorTicksCount);
|
||||
step = parseFloat(parseFloat(step).toPrecision(12));
|
||||
|
||||
var valueInt = settings.valueInt || 3;
|
||||
|
||||
var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
|
||||
? settings.valueDec : 2;
|
||||
|
||||
step = parseFloat(parseFloat(step).toFixed(valueDec));
|
||||
|
||||
var majorTicks = [];
|
||||
var highlights = [];
|
||||
@ -44,7 +50,7 @@ export default class TbAnalogueLinearGauge {
|
||||
var majorTick = tick + minValue;
|
||||
majorTicks.push(majorTick);
|
||||
var nextTick = tick+step;
|
||||
nextTick = parseFloat(parseFloat(nextTick).toPrecision(12));
|
||||
nextTick = parseFloat(parseFloat(nextTick).toFixed(valueDec));
|
||||
if (tick<total) {
|
||||
var highlightColor = tinycolor(keyColor);
|
||||
var percent = tick/total;
|
||||
@ -89,9 +95,8 @@ export default class TbAnalogueLinearGauge {
|
||||
// borders
|
||||
|
||||
// number formats
|
||||
valueInt: settings.valueInt || 3,
|
||||
valueDec: (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
|
||||
? settings.valueDec : 2,
|
||||
valueInt: valueInt,
|
||||
valueDec: valueDec,
|
||||
majorTicksInt: 1,
|
||||
majorTicksDec: 0,
|
||||
|
||||
|
||||
@ -35,7 +35,13 @@ export default class TbAnalogueRadialGauge {
|
||||
var majorTicksCount = settings.majorTicksCount || 10;
|
||||
var total = maxValue-minValue;
|
||||
var step = (total/majorTicksCount);
|
||||
step = parseFloat(parseFloat(step).toPrecision(12));
|
||||
|
||||
var valueInt = settings.valueInt || 3;
|
||||
|
||||
var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
|
||||
? settings.valueDec : 2;
|
||||
|
||||
step = parseFloat(parseFloat(step).toFixed(valueDec));
|
||||
|
||||
var majorTicks = [];
|
||||
var highlights = [];
|
||||
@ -44,7 +50,7 @@ export default class TbAnalogueRadialGauge {
|
||||
while(tick<=maxValue) {
|
||||
majorTicks.push(tick);
|
||||
var nextTick = tick+step;
|
||||
nextTick = parseFloat(parseFloat(nextTick).toPrecision(12));
|
||||
nextTick = parseFloat(parseFloat(nextTick).toFixed(valueDec));
|
||||
if (tick<maxValue) {
|
||||
var highlightColor = tinycolor(keyColor);
|
||||
var percent = (tick-minValue)/total;
|
||||
@ -86,9 +92,8 @@ export default class TbAnalogueRadialGauge {
|
||||
//borderShadowWidth: (settings.showBorder !== false) ? 3 : 0,
|
||||
|
||||
// number formats
|
||||
valueInt: settings.valueInt || 3,
|
||||
valueDec: (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
|
||||
? settings.valueDec : 2,
|
||||
valueInt: valueInt,
|
||||
valueDec: valueDec,
|
||||
majorTicksInt: 1,
|
||||
majorTicksDec: 0,
|
||||
|
||||
|
||||
@ -40,7 +40,9 @@
|
||||
"refresh": "Refresh",
|
||||
"undo": "Undo",
|
||||
"copy": "Copy",
|
||||
"paste": "Paste"
|
||||
"paste": "Paste",
|
||||
"import": "Import",
|
||||
"export": "Export"
|
||||
},
|
||||
"admin": {
|
||||
"general": "General",
|
||||
@ -214,7 +216,19 @@
|
||||
"vertical-margin-required": "Vertical margin value is required.",
|
||||
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
|
||||
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
|
||||
"display-title": "Display dashboard title"
|
||||
"display-title": "Display dashboard title",
|
||||
"import": "Import dashboard",
|
||||
"export": "Export dashboard",
|
||||
"export-failed-error": "Unable to export dashboard: {error}",
|
||||
"create-new-dashboard": "Create new dashboard",
|
||||
"dashboard-file": "Dashboard file",
|
||||
"invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.",
|
||||
"dashboard-import-missing-aliases-title": "Select missing devices for dashboard aliases",
|
||||
"create-new-widget": "Create new widget",
|
||||
"import-widget": "Import widget",
|
||||
"widget-file": "Widget file",
|
||||
"invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.",
|
||||
"widget-import-missing-aliases-title": "Select missing devices used by widget"
|
||||
},
|
||||
"datakey": {
|
||||
"settings": "Settings",
|
||||
@ -370,6 +384,10 @@
|
||||
"avatar": "Avatar",
|
||||
"open-user-menu": "Open user menu"
|
||||
},
|
||||
"import": {
|
||||
"no-file": "No file selected",
|
||||
"drop-file": "Drop a JSON file or click to select a file to upload."
|
||||
},
|
||||
"item": {
|
||||
"selected": "Selected"
|
||||
},
|
||||
@ -612,7 +630,8 @@
|
||||
"widget-type-load-failed-error": "Failed to load widget type!",
|
||||
"widget-template-load-failed-error": "Failed to load widget template!",
|
||||
"add": "Add Widget",
|
||||
"undo": "Undo widget changes"
|
||||
"undo": "Undo widget changes",
|
||||
"export": "Export widget"
|
||||
},
|
||||
"widgets-bundle": {
|
||||
"current": "Current bundle",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user