Browse Source

UI: Add new widget types. Improve dashboard settings.

pull/18/head
Igor Kulikov 10 years ago
parent
commit
b5581a8a32
  1. 18
      dao/src/main/resources/system-data.cql
  2. 3
      ui/.eslintrc
  3. 2
      ui/package.json
  4. 2
      ui/src/app/app.js
  5. 3
      ui/src/app/app.run.js
  6. 10
      ui/src/app/components/dashboard.directive.js
  7. 112
      ui/src/app/components/dashboard.tpl.html
  8. 105
      ui/src/app/components/react/json-form-image.jsx
  9. 79
      ui/src/app/components/react/json-form-image.scss
  10. 2
      ui/src/app/components/react/json-form-schema-form.jsx
  11. 64
      ui/src/app/dashboard/dashboard-settings.controller.js
  12. 91
      ui/src/app/dashboard/dashboard-settings.scss
  13. 115
      ui/src/app/dashboard/dashboard-settings.tpl.html
  14. 29
      ui/src/app/dashboard/dashboard.controller.js
  15. 14
      ui/src/app/dashboard/dashboard.tpl.html
  16. 2
      ui/src/app/dashboard/index.js
  17. 7
      ui/src/app/device/device-fieldset.tpl.html
  18. 13
      ui/src/app/device/device.directive.js
  19. 23
      ui/src/locale/en_US.json

18
dao/src/main/resources/system-data.cql

File diff suppressed because one or more lines are too long

3
ui/.eslintrc

@ -11,5 +11,8 @@
"node_modules",
"\\.tpl\\.html$"
]
},
"globals": {
"FileReader": true
}
}

2
ui/package.json

@ -14,6 +14,7 @@
"build": "NODE_ENV=production webpack -p"
},
"dependencies": {
"@flowjs/ng-flow": "^2.7.1",
"ace-builds": "^1.2.5",
"angular": "1.5.8",
"angular-animate": "1.5.8",
@ -65,6 +66,7 @@
"react": "^15.4.1",
"react-ace": "^4.1.0",
"react-dom": "^15.4.1",
"react-dropzone": "^3.7.3",
"react-schema-form": "^0.3.1",
"react-tap-event-plugin": "^2.0.1",
"reactcss": "^1.0.9",

2
ui/src/app/app.js

@ -42,6 +42,7 @@ import 'react-dom';
import 'material-ui';
import 'react-schema-form';
import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import thingsboardLogin from './login';
import thingsboardDialogs from './components/datakey-config-dialog.controller';
@ -88,6 +89,7 @@ angular.module('thingsboard', [
'angular-carousel',
'ngclipboard',
react.name,
'flow',
thingsboardLogin,
thingsboardDialogs,
thingsboardMenu,

3
ui/src/app/app.run.js

@ -14,9 +14,12 @@
* limitations under the License.
*/
import Flow from '@flowjs/ng-flow/dist/ng-flow-standalone.min';
/*@ngInject*/
export default function AppRun($rootScope, $window, $log, $state, $mdDialog, $filter, loginService, userService, $translate) {
$window.Flow = Flow;
var frame = $window.frameElement;
var unauthorizedDialog = null;
var forbiddenDialog = null;

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

@ -51,6 +51,7 @@ function Dashboard() {
widgets: '=',
deviceAliasList: '=',
columns: '=',
margins: '=',
isEdit: '=',
isMobile: '=',
isMobileDisabled: '=?',
@ -61,7 +62,8 @@ function Dashboard() {
onWidgetClicked: '&?',
loadWidgets: '&?',
onInit: '&?',
onInitFailed: '&?'
onInitFailed: '&?',
dashboardStyle: '=?'
},
controller: DashboardController,
controllerAs: 'vm',
@ -108,7 +110,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
},
isMobile: vm.isMobileDisabled ? false : vm.isMobile,
mobileBreakPoint: vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960),
margins: [10, 10],
margins: vm.margins ? vm.margins : [10, 10],
saveGridItemCalculatedHeightInMobile: true
};
@ -161,6 +163,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
vm.gridsterOpts.columns = vm.columns ? vm.columns : 24;
});
$scope.$watch('vm.margins', function () {
vm.gridsterOpts.margins = vm.margins ? vm.margins : [10, 10];
});
$scope.$watch('vm.isEdit', function () {
vm.gridsterOpts.resizable.enabled = vm.isEdit;
vm.gridsterOpts.draggable.enabled = vm.isEdit;

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

@ -20,61 +20,63 @@
<md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
</md-content>
<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap>
<div id="gridster-child" gridster="vm.gridsterOpts">
<ul>
<!-- ng-click="widgetClicked($event, widget)" -->
<li gridster-item="widget" ng-repeat="widget in vm.widgets">
<div tb-expand-fullscreen expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
tb-mousedown="vm.widgetMouseDown($event, widget)"
tb-mousemove="vm.widgetMouseMove($event, widget)"
tb-mouseup="vm.widgetMouseUp($event, widget)"
style="
cursor: pointer;
color: {{vm.widgetColor(widget)}};
background-color: {{vm.widgetBackgroundColor(widget)}};
padding: {{vm.widgetPadding(widget)}}
">
<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
<div class="tb-widget-actions" layout="row" layout-align="start center">
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button md-primary"></md-button>
<md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.editWidget($event, widget)"
aria-label="{{ 'widget.edit' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.edit' | translate }}
</md-tooltip>
<md-icon class="material-icons">
edit
</md-icon>
</md-button>
<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.removeWidget($event, widget)"
aria-label="{{ 'widget.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.remove' | translate }}
</md-tooltip>
<md-icon class="material-icons">
close
</md-icon>
</md-button>
</div>
<div flex layout="column" class="tb-widget-content">
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }">
<div ng-style="vm.dashboardStyle" id="gridster-background" style="height: auto; min-height: 100%;">
<div id="gridster-child" gridster="vm.gridsterOpts">
<ul>
<!-- ng-click="widgetClicked($event, widget)" -->
<li gridster-item="widget" ng-repeat="widget in vm.widgets">
<div tb-expand-fullscreen expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
tb-mousedown="vm.widgetMouseDown($event, widget)"
tb-mousemove="vm.widgetMouseMove($event, widget)"
tb-mouseup="vm.widgetMouseUp($event, widget)"
style="
cursor: pointer;
color: {{vm.widgetColor(widget)}};
background-color: {{vm.widgetBackgroundColor(widget)}};
padding: {{vm.widgetPadding(widget)}}
">
<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
</div>
</div>
</li>
</ul>
<div class="tb-widget-actions" layout="row" layout-align="start center">
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button md-primary"></md-button>
<md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.editWidget($event, widget)"
aria-label="{{ 'widget.edit' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.edit' | translate }}
</md-tooltip>
<md-icon class="material-icons">
edit
</md-icon>
</md-button>
<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.removeWidget($event, widget)"
aria-label="{{ 'widget.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.remove' | translate }}
</md-tooltip>
<md-icon class="material-icons">
close
</md-icon>
</md-button>
</div>
<div flex layout="column" class="tb-widget-content">
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }">
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</md-content>

105
ui/src/app/components/react/json-form-image.jsx

@ -0,0 +1,105 @@
/*
* 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 './json-form-image.scss';
import React from 'react';
import ThingsboardBaseComponent from './json-form-base-component.jsx';
import Dropzone from 'react-dropzone';
import IconButton from 'material-ui/IconButton';
class ThingsboardImage extends React.Component {
constructor(props) {
super(props);
this.onValueChanged = this.onValueChanged.bind(this);
this.onDrop = this.onDrop.bind(this);
this.onClear = this.onClear.bind(this);
var value = props.value ? props.value + '' : null;
this.state = {
imageUrl: value
};
}
onValueChanged(value) {
this.setState({
imageUrl: value
});
this.props.onChangeValidate({
target: {
value: value
}
});
}
onDrop(files) {
var reader = new FileReader();
reader.onload = (function(tImg) {
return function(event) {
tImg.onValueChanged(event.target.result);
};
})(this);
reader.readAsDataURL(files[0]);
}
onClear(event) {
if (event) {
event.stopPropagation();
}
this.onValueChanged(null);
}
render() {
var labelClass = "tb-label";
if (this.props.form.required) {
labelClass += " tb-required";
}
if (this.props.form.readonly) {
labelClass += " tb-readonly";
}
if (this.state.focused) {
labelClass += " tb-focused";
}
var previewComponent;
if (this.state.imageUrl) {
previewComponent = <img className="tb-image-preview" src={this.state.imageUrl} />;
} else {
previewComponent = <div>No image selected</div>;
}
return (
<div className="tb-container">
<label className={labelClass}>{this.props.form.title}</label>
<div className="tb-image-select-container">
<div className="tb-image-preview-container">{previewComponent}</div>
<div className="tb-image-clear-container">
<IconButton className="tb-image-clear-btn" iconClassName="material-icons" tooltip="Clear" onTouchTap={this.onClear}>clear</IconButton>
</div>
<Dropzone className="tb-dropzone"
onDrop={this.onDrop}
multiple={false}
accept="image/*">
<div>Drop an image or click to select a file to upload.</div>
</Dropzone>
</div>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardImage);

79
ui/src/app/components/react/json-form-image.scss

@ -0,0 +1,79 @@
/**
* 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;
.tb-image-select-container {
position: relative;
height: $previewSize;
width: 100%;
}
.tb-image-preview {
max-width: $previewSize;
max-height: $previewSize;
width: 100%;
height: 100%;
}
.tb-image-preview-container {
position: relative;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
border: solid 1px;
vertical-align: top;
float: left;
div {
width: 100%;
font-size: 18px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
.tb-dropzone {
position: relative;
border: dashed 2px;
height: $previewSize;
vertical-align: top;
padding: 0 8px;
overflow: hidden;
div {
width: 100%;
font-size: 24px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
.tb-image-clear-container {
width: 48px;
height: $previewSize;
position: relative;
float: right;
}
.tb-image-clear-btn {
position: absolute !important;
top: 50%;
transform: translate(0%,-50%) !important;
}

2
ui/src/app/components/react/json-form-schema-form.jsx

@ -26,6 +26,7 @@ import ThingsboardText from './json-form-text.jsx';
import Select from 'react-schema-form/lib/Select';
import Radios from 'react-schema-form/lib/Radios';
import ThingsboardDate from './json-form-date.jsx';
import ThingsboardImage from './json-form-image.jsx';
import ThingsboardCheckbox from './json-form-checkbox.jsx';
import Help from 'react-schema-form/lib/Help';
import ThingsboardFieldSet from './json-form-fieldset.jsx';
@ -45,6 +46,7 @@ class ThingsboardSchemaForm extends React.Component {
'select': Select,
'radios': Radios,
'date': ThingsboardDate,
'image': ThingsboardImage,
'checkbox': ThingsboardCheckbox,
'help': Help,
'array': ThingsboardArray,

64
ui/src/app/dashboard/dashboard-settings.controller.js

@ -0,0 +1,64 @@
/*
* 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 './dashboard-settings.scss';
/*@ngInject*/
export default function DashboardSettingsController($scope, $mdDialog, gridSettings) {
var vm = this;
vm.cancel = cancel;
vm.save = save;
vm.imageAdded = imageAdded;
vm.clearImage = clearImage;
vm.gridSettings = gridSettings || {};
vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
vm.gridSettings.columns = vm.gridSettings.columns || 24;
vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
vm.hMargin = vm.gridSettings.margins[0];
vm.vMargin = vm.gridSettings.margins[1];
function cancel() {
$mdDialog.cancel();
}
function imageAdded($file) {
var reader = new FileReader();
reader.onload = function(event) {
$scope.$apply(function() {
if (event.target.result && event.target.result.startsWith('data:image/')) {
$scope.theForm.$setDirty();
vm.gridSettings.backgroundImageUrl = event.target.result;
}
});
};
reader.readAsDataURL($file.file);
}
function clearImage() {
$scope.theForm.$setDirty();
vm.gridSettings.backgroundImageUrl = null;
}
function save() {
$scope.theForm.$setPristine();
vm.gridSettings.margins = [vm.hMargin, vm.vMargin];
$mdDialog.hide(vm.gridSettings);
}
}

91
ui/src/app/dashboard/dashboard-settings.scss

@ -0,0 +1,91 @@
/**
* 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-image-select-container {
position: relative;
height: $previewSize;
width: 100%;
}
.tb-image-preview {
max-width: $previewSize;
max-height: $previewSize;
width: auto;
height: auto;
}
.tb-image-preview-container {
position: relative;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
border: solid 1px;
vertical-align: top;
float: left;
div {
width: 100%;
font-size: 18px;
text-align: center;
}
div, .tb-image-preview {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
}
.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-image-clear-container {
width: 48px;
height: $previewSize;
position: relative;
float: right;
}
.tb-image-clear-btn {
position: absolute !important;
top: 50%;
transform: translate(0%,-50%) !important;
}

115
ui/src/app/dashboard/dashboard-settings.tpl.html

@ -0,0 +1,115 @@
<!--
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="{{ 'dashboard.settings' | translate }}">
<form name="theForm" ng-submit="vm.save()">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 translate>dashboard.settings</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">
<md-input-container class="md-block">
<label translate>dashboard.columns-count</label>
<input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"
max="1000" />
<div ng-messages="theForm.columns.$error" multiple md-auto-hide="false">
<div ng-message="required" translate>dashboard.columns-count-required</div>
<div ng-message="min" translate>dashboard.min-columns-count-message</div>
<div ng-message="max">dashboard.max-columns-count-message</div>
</div>
</md-input-container>
<small translate>dashboard.widgets-margins</small>
<div flex layout="row">
<md-input-container flex class="md-block">
<label translate>dashboard.horizontal-margin</label>
<input required type="number" step="any" name="hMargin" ng-model="vm.hMargin" min="0"
max="50" />
<div ng-messages="theForm.hMargin.$error" multiple md-auto-hide="false">
<div ng-message="required" translate>dashboard.horizontal-margin-required</div>
<div ng-message="min" translate>dashboard.min-horizontal-margin-message</div>
<div ng-message="max" translate>dashboard.max-horizontal-margin-message</div>
</div>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>dashboard.vertical-margin</label>
<input required type="number" step="any" name="vMargin" ng-model="vm.vMargin" min="0"
max="50" />
<div ng-messages="theForm.vMargin.$error" multiple md-auto-hide="false">
<div ng-message="required" translate>dashboard.vertical-margin-required</div>
<div ng-message="min" translate>dashboard.min-vertical-margin-message</div>
<div ng-message="max" translate>dashboard.max-vertical-margin-message</div>
</div>
</md-input-container>
</div>
<div flex
ng-required="false"
md-color-picker
ng-model="vm.gridSettings.backgroundColor"
label="{{ 'dashboard.background-color' | translate }}"
icon="format_color_fill"
default="rgba(0,0,0,0)"
md-color-clear-button="false"
open-on-input="true"
md-color-generic-palette="false"
md-color-history="false"
></div>
<div class="tb-container">
<label class="tb-label" translate>dashboard.background-image</label>
<div flow-init="{singleFile:true}"
flow-file-added="vm.imageAdded( $file )" class="tb-image-select-container">
<div class="tb-image-preview-container">
<div ng-show="!vm.gridSettings.backgroundImageUrl" translate>dashboard.no-image</div>
<img ng-show="vm.gridSettings.backgroundImageUrl" class="tb-image-preview" src="{{vm.gridSettings.backgroundImageUrl}}" />
</div>
<div class="tb-image-clear-container">
<md-button ng-click="vm.clearImage()"
class="tb-image-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>dashboard.drop-image</label>
<input class="file-input" flow-btn flow-attrs="{accept:'image/*'}" id="select">
</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" type="submit" class="md-raised md-primary">
{{ 'action.save' | 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>

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

@ -16,6 +16,7 @@
/* eslint-disable import/no-unresolved, import/default */
import deviceAliasesTemplate from './device-aliases.tpl.html';
import dashboardBackgroundTemplate from './dashboard-settings.tpl.html';
import addWidgetTemplate from './add-widget.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@ -55,6 +56,7 @@ export default function DashboardController(types, widgetService, userService,
vm.onAddWidgetClosed = onAddWidgetClosed;
vm.onEditWidgetClosed = onEditWidgetClosed;
vm.openDeviceAliases = openDeviceAliases;
vm.openDashboardSettings = openDashboardSettings;
vm.removeWidget = removeWidget;
vm.saveDashboard = saveDashboard;
vm.saveWidget = saveWidget;
@ -252,6 +254,24 @@ export default function DashboardController(types, widgetService, userService,
});
}
function openDashboardSettings($event) {
$mdDialog.show({
controller: 'DashboardSettingsController',
controllerAs: 'vm',
templateUrl: dashboardBackgroundTemplate,
locals: {
gridSettings: angular.copy(vm.dashboard.configuration.gridSettings)
},
parent: angular.element($document[0].body),
skipHide: true,
fullscreen: true,
targetEvent: $event
}).then(function (gridSettings) {
vm.dashboard.configuration.gridSettings = gridSettings;
}, function () {
});
}
function editWidget($event, widget) {
$event.stopPropagation();
var newEditingIndex = vm.widgets.indexOf(widget);
@ -368,6 +388,15 @@ export default function DashboardController(types, widgetService, userService,
w.triggerHandler('resize');
}
}).then(function (widget) {
var columns = 24;
if (vm.dashboard.configuration.gridSettings && vm.dashboard.configuration.gridSettings.columns) {
columns = vm.dashboard.configuration.gridSettings.columns;
}
if (columns != 24) {
var ratio = columns / 24;
widget.sizeX *= ratio;
widget.sizeY *= ratio;
}
vm.widgets.push(widget);
}, function () {
});

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

@ -59,10 +59,22 @@
<md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDeviceAliases($event)">
{{ 'device.aliases' | translate }}
</md-button>
<md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDashboardSettings($event)">
{{ 'dashboard.settings' | translate }}
</md-button>
</section>
<div class="tb-absolute-fill" ng-class="{ 'tb-padded' : !vm.widgetEditMode, 'tb-shrinked' : vm.isEditingWidget }">
<div class="tb-absolute-fill"
ng-class="{ 'tb-padded' : !vm.widgetEditMode, 'tb-shrinked' : vm.isEditingWidget }">
<tb-dashboard
dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
'background-repeat': 'no-repeat',
'background-attachment': 'scroll',
'background-size': '100%',
'background-position': '0% 0%'}"
widgets="vm.widgets"
columns="vm.dashboard.configuration.gridSettings.columns"
margins="vm.dashboard.configuration.gridSettings.margins"
device-alias-list="vm.dashboard.configuration.deviceAliases"
is-edit="vm.isEdit || vm.widgetEditMode"
is-mobile="vm.forceDashboardMobileMode"

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

@ -34,6 +34,7 @@ import DashboardRoutes from './dashboard.routes';
import DashboardsController from './dashboards.controller';
import DashboardController from './dashboard.controller';
import DeviceAliasesController from './device-aliases.controller';
import DashboardSettingsController from './dashboard-settings.controller';
import AssignDashboardToCustomerController from './assign-to-customer.controller';
import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
import AddWidgetController from './add-widget.controller';
@ -59,6 +60,7 @@ export default angular.module('thingsboard.dashboard', [
.controller('DashboardsController', DashboardsController)
.controller('DashboardController', DashboardController)
.controller('DeviceAliasesController', DeviceAliasesController)
.controller('DashboardSettingsController', DashboardSettingsController)
.controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
.controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
.controller('AddWidgetController', AddWidgetController)

7
ui/src/app/device/device-fieldset.tpl.html

@ -36,6 +36,13 @@
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>device.copyId</span>
</md-button>
<md-button ngclipboard data-clipboard-action="copy"
ngclipboard-success="onAccessTokenCopied(e)"
data-clipboard-text="{{deviceCredentials.credentialsId}}" ng-show="!isEdit"
class="md-raised">
<md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
<span translate>device.copyAccessToken</span>
</md-button>
</div>
<md-content class="md-padding" layout="column">

13
ui/src/app/device/device.directive.js

@ -20,18 +20,23 @@ import deviceFieldsetTemplate from './device-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function DeviceDirective($compile, $templateCache, toast, $translate, types, customerService) {
export default function DeviceDirective($compile, $templateCache, toast, $translate, types, deviceService, customerService) {
var linker = function (scope, element) {
var template = $templateCache.get(deviceFieldsetTemplate);
element.html(template);
scope.isAssignedToCustomer = false;
scope.assignedCustomer = null;
scope.deviceCredentials = null;
scope.$watch('device', function(newVal) {
if (newVal) {
deviceService.getDeviceCredentials(scope.device.id.id).then(
function success(credentials) {
scope.deviceCredentials = credentials;
}
);
if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) {
scope.isAssignedToCustomer = true;
customerService.getCustomer(scope.device.customerId.id).then(
@ -50,6 +55,10 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
toast.showSuccess($translate.instant('device.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
scope.onAccessTokenCopied = function() {
toast.showSuccess($translate.instant('device.accessTokenCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
};
$compile(element.contents())(scope);
}
return {

23
ui/src/locale/en_US.json

@ -192,7 +192,26 @@
"select-existing": "Select existing dashboard",
"create-new": "Create new dashboard",
"new-dashboard-title": "New dashboard title",
"open-dashboard": "Open dashboard"
"open-dashboard": "Open dashboard",
"set-background": "Set background",
"background-color": "Background color",
"background-image": "Background image",
"no-image": "No image selected",
"drop-image": "Drop an image or click to select a file to upload.",
"settings": "Settings",
"columns-count": "Columns count",
"columns-count-required": "Columns count is required.",
"min-columns-count-message": "Only 10 minimum column count is allowed.",
"max-columns-count-message": "Only 1000 maximum column count is allowed.",
"widgets-margins": "Margin between widgets",
"horizontal-margin": "Horizontal margin",
"horizontal-margin-required": "Horizontal margin value is required.",
"min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.",
"max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.",
"vertical-margin": "Vertical margin",
"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."
},
"datakey": {
"settings": "Settings",
@ -280,7 +299,9 @@
"events": "Events",
"details": "Details",
"copyId": "Copy device Id",
"copyAccessToken": "Copy access token",
"idCopiedMessage": "Device Id has been copied to clipboard",
"accessTokenCopiedMessage": "Device access token has been copied to clipboard",
"assignedToCustomer": "Assigned to customer",
"unable-delete-device-alias-title": "Unable to delete device alias",
"unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}"

Loading…
Cancel
Save