Import/Export. Bug fixes.

This commit is contained in:
Igor Kulikov 2017-01-06 19:49:00 +02:00
parent f78b8e55f1
commit 9c616bd3a3
28 changed files with 928 additions and 134 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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;
}

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

View 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 */

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

View File

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

View File

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

View File

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

View File

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