-
- {{widget.config.title}}
-
-
-
-
-
-
- {{ 'widget.edit' | translate }}
-
-
- edit
-
-
-
-
- {{ 'widget.remove' | translate }}
-
-
- close
-
-
-
-
-
+
+
-
-
+
+
+
+
+ {{ 'widget.edit' | translate }}
+
+
+ edit
+
+
+
+
+ {{ 'widget.remove' | translate }}
+
+
+ close
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/app/components/react/json-form-image.jsx b/ui/src/app/components/react/json-form-image.jsx
new file mode 100644
index 0000000000..2da3edc2bd
--- /dev/null
+++ b/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 =

;
+ } else {
+ previewComponent =
No image selected
;
+ }
+
+ return (
+
+
+
+
{previewComponent}
+
+ clear
+
+
+ Drop an image or click to select a file to upload.
+
+
+
+ );
+ }
+}
+
+export default ThingsboardBaseComponent(ThingsboardImage);
\ No newline at end of file
diff --git a/ui/src/app/components/react/json-form-image.scss b/ui/src/app/components/react/json-form-image.scss
new file mode 100644
index 0000000000..58b5eaaf9e
--- /dev/null
+++ b/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;
+}
diff --git a/ui/src/app/components/react/json-form-schema-form.jsx b/ui/src/app/components/react/json-form-schema-form.jsx
index 3e1c046b3b..c641a0acaf 100644
--- a/ui/src/app/components/react/json-form-schema-form.jsx
+++ b/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,
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index 565c8a1b06..fbc559beff 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -159,6 +159,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
};
vm.gridsterItemInitialized = gridsterItemInitialized;
+ vm.visibleRectChanged = visibleRectChanged;
function gridsterItemInitialized(item) {
if (item) {
@@ -167,6 +168,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}
}
+ function visibleRectChanged(newVisibleRect) {
+ visibleRect = newVisibleRect;
+ updateVisibility();
+ }
+
initWidget();
function initWidget() {
@@ -221,11 +227,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
$scope.$emit("widgetPositionChanged", widget);
});
- $scope.$on('visibleRectChanged', function (event, newVisibleRect) {
- visibleRect = newVisibleRect;
- updateVisibility();
- });
-
$scope.$on('onWidgetFullscreenChanged', function(event, isWidgetExpanded, fullscreenWidget) {
if (widget === fullscreenWidget) {
onRedraw(0);
@@ -318,9 +319,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function onRedraw(delay, dataUpdate) {
- if (!visible) {
+ //TODO:
+ /*if (!visible) {
return;
- }
+ }*/
if (angular.isUndefined(delay)) {
delay = 0;
}
diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget.directive.js
index 6ba25c3c4f..b6000cbd79 100644
--- a/ui/src/app/components/widget.directive.js
+++ b/ui/src/app/components/widget.directive.js
@@ -34,12 +34,19 @@ function Widget($controller, $compile, widgetService) {
var widget = locals.widget;
var gridsterItem;
+ scope.$on('visibleRectChanged', function (event, newVisibleRect) {
+ locals.visibleRect = newVisibleRect;
+ if (widgetController) {
+ widgetController.visibleRectChanged(newVisibleRect);
+ }
+ });
+
scope.$on('gridster-item-initialized', function (event, item) {
gridsterItem = item;
if (widgetController) {
widgetController.gridsterItemInitialized(gridsterItem);
}
- })
+ });
elem.html('
' +
'
' +
diff --git a/ui/src/app/dashboard/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
new file mode 100644
index 0000000000..d15359e31a
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/ui/src/app/dashboard/dashboard-settings.scss b/ui/src/app/dashboard/dashboard-settings.scss
new file mode 100644
index 0000000000..2231372075
--- /dev/null
+++ b/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;
+}
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
new file mode 100644
index 0000000000..f69eb02a51
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -0,0 +1,115 @@
+
+
+
+
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index 18c2b5f4e9..68978c34f1 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/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 () {
});
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index b18c392d83..0c815a651e 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -59,10 +59,22 @@
{{ 'device.aliases' | translate }}
+
+ {{ 'dashboard.settings' | translate }}
+
-
+
device.copyId
+
+
+ device.copyAccessToken
+
diff --git a/ui/src/app/device/device.directive.js b/ui/src/app/device/device.directive.js
index 918040bd67..7f840164a7 100644
--- a/ui/src/app/device/device.directive.js
+++ b/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 {
diff --git a/ui/src/locale/en_US.json b/ui/src/locale/en_US.json
index 4a5e48d900..9da68d6182 100644
--- a/ui/src/locale/en_US.json
+++ b/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):
{{widgetsList}}"