Browse Source

Import/Export. Bug fixes.

pull/33/head
Igor Kulikov 10 years ago
parent
commit
9c616bd3a3
  1. 1
      ui/package.json
  2. 4
      ui/src/app/api/device.service.js
  3. 13
      ui/src/app/components/dashboard.directive.js
  4. 12
      ui/src/app/components/dashboard.tpl.html
  5. 4
      ui/src/app/components/grid.directive.js
  6. 23
      ui/src/app/components/grid.tpl.html
  7. 10
      ui/src/app/components/react/json-form-number.jsx
  8. 10
      ui/src/app/dashboard/add-widget.controller.js
  9. 1
      ui/src/app/dashboard/dashboard-fieldset.tpl.html
  10. 96
      ui/src/app/dashboard/dashboard.controller.js
  11. 1
      ui/src/app/dashboard/dashboard.directive.js
  12. 43
      ui/src/app/dashboard/dashboard.tpl.html
  13. 38
      ui/src/app/dashboard/dashboards.controller.js
  14. 1
      ui/src/app/dashboard/dashboards.tpl.html
  15. 55
      ui/src/app/dashboard/device-aliases.controller.js
  16. 6
      ui/src/app/dashboard/device-aliases.tpl.html
  17. 10
      ui/src/app/dashboard/edit-widget.directive.js
  18. 2
      ui/src/app/dashboard/index.js
  19. 11
      ui/src/app/global-interceptor.service.js
  20. 71
      ui/src/app/import-export/import-dialog.controller.js
  21. 70
      ui/src/app/import-export/import-dialog.scss
  22. 72
      ui/src/app/import-export/import-dialog.tpl.html
  23. 373
      ui/src/app/import-export/import-export.service.js
  24. 24
      ui/src/app/import-export/index.js
  25. 56
      ui/src/app/services/item-buffer.service.js
  26. 15
      ui/src/app/widget/lib/analogue-linear-gauge.js
  27. 15
      ui/src/app/widget/lib/analogue-radial-gauge.js
  28. 25
      ui/src/locale/en_US.json

1
ui/package.json

@ -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",

4
ui/src/app/api/device.service.js

@ -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);

13
ui/src/app/components/dashboard.directive.js

@ -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) {

12
ui/src/app/components/dashboard.tpl.html

@ -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"

4
ui/src/app/components/grid.directive.js

@ -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 () {
};

23
ui/src/app/components/grid.tpl.html

@ -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>

10
ui/src/app/components/react/json-form-number.jsx

@ -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}

10
ui/src/app/dashboard/add-widget.controller.js

@ -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,

1
ui/src/app/dashboard/dashboard-fieldset.tpl.html

@ -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">

96
ui/src/app/dashboard/dashboard.controller.js

@ -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() {

1
ui/src/app/dashboard/dashboard.directive.js

@ -36,6 +36,7 @@ export default function DashboardDirective($compile, $templateCache) {
theForm: '=',
onAssignToCustomer: '&',
onUnassignFromCustomer: '&',
onExportDashboard: '&',
onDeleteDashboard: '&'
}
};

43
ui/src/app/dashboard/dashboard.tpl.html

@ -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 }}"

38
ui/src/app/dashboard/dashboards.controller.js

@ -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)

1
ui/src/app/dashboard/dashboards.tpl.html

@ -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>

55
ui/src/app/dashboard/device-aliases.controller.js

@ -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);

6
ui/src/app/dashboard/device-aliases.tpl.html

@ -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 }}

10
ui/src/app/dashboard/edit-widget.directive.js

@ -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,

2
ui/src/app/dashboard/index.js

@ -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,

11
ui/src/app/global-interceptor.service.js

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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;

56
ui/src/app/services/item-buffer.service.js

@ -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);
}
}

15
ui/src/app/widget/lib/analogue-linear-gauge.js

@ -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,

15
ui/src/app/widget/lib/analogue-radial-gauge.js

@ -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,

25
ui/src/locale/en_US.json

@ -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…
Cancel
Save