Add social share buttons for public dashboards sharing. Improve dashboard toolbar configuration. Add project version information to bottom right part of the dashboard.

This commit is contained in:
Igor Kulikov 2017-05-03 14:24:26 +03:00
parent 912c572aed
commit bfd654d84f
17 changed files with 290 additions and 29 deletions

View File

@ -33,6 +33,7 @@
"angular-messages": "1.5.8", "angular-messages": "1.5.8",
"angular-route": "1.5.8", "angular-route": "1.5.8",
"angular-sanitize": "1.5.8", "angular-sanitize": "1.5.8",
"angular-socialshare": "^2.3.8",
"angular-storage": "0.0.15", "angular-storage": "0.0.15",
"angular-touch": "1.5.8", "angular-touch": "1.5.8",
"angular-translate": "2.13.1", "angular-translate": "2.13.1",

View File

@ -19,6 +19,7 @@ import angular from 'angular';
import ngMaterial from 'angular-material'; import ngMaterial from 'angular-material';
import ngMdIcons from 'angular-material-icons'; import ngMdIcons from 'angular-material-icons';
import ngCookies from 'angular-cookies'; import ngCookies from 'angular-cookies';
import angularSocialshare from 'angular-socialshare';
import 'angular-translate'; import 'angular-translate';
import 'angular-translate-loader-static-files'; import 'angular-translate-loader-static-files';
import 'angular-translate-storage-local'; import 'angular-translate-storage-local';
@ -82,6 +83,7 @@ angular.module('thingsboard', [
ngMaterial, ngMaterial,
ngMdIcons, ngMdIcons,
ngCookies, ngCookies,
angularSocialshare,
'pascalprecht.translate', 'pascalprecht.translate',
'mdColorPicker', 'mdColorPicker',
mdPickers, mdPickers,

View File

@ -106,7 +106,8 @@ function Utils($mdColorPalette, $rootScope, $window, $q, deviceService, types) {
isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty, isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
filterSearchTextEntities: filterSearchTextEntities, filterSearchTextEntities: filterSearchTextEntities,
guid: guid, guid: guid,
createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
isLocalUrl: isLocalUrl
} }
return service; return service;
@ -428,4 +429,15 @@ function Utils($mdColorPalette, $rootScope, $window, $q, deviceService, types) {
return deferred.promise; return deferred.promise;
} }
function isLocalUrl(url) {
var parser = document.createElement('a'); //eslint-disable-line
parser.href = url;
var host = parser.hostname;
if (host === "localhost" || host === "127.0.0.1") {
return true;
} else {
return false;
}
}
} }

View File

@ -0,0 +1,58 @@
/*
* Copyright © 2016-2017 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 socialsharePanelTemplate from './socialshare-panel.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.socialsharePanel', [])
.directive('tbSocialSharePanel', SocialsharePanel)
.name;
/*@ngInject*/
function SocialsharePanel() {
return {
restrict: "E",
scope: true,
bindToController: {
shareTitle: '@',
shareText: '@',
shareLink: '@',
shareHashTags: '@'
},
controller: SocialsharePanelController,
controllerAs: 'vm',
templateUrl: socialsharePanelTemplate
};
}
/*@ngInject*/
function SocialsharePanelController(utils) {
let vm = this;
vm.isShareLinkLocal = function() {
if (vm.shareLink && vm.shareLink.length > 0) {
return utils.isLocalUrl(vm.shareLink);
} else {
return true;
}
}
}

View File

@ -0,0 +1,62 @@
<!--
Copyright © 2016-2017 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.
-->
<div layout="row" ng-show="!vm.isShareLinkLocal()">
<md-button class="md-icon-button md-raised md-primary"
socialshare
socialshare-provider="facebook"
socialshare-title="{{ vm.shareTitle }}"
socialshare-text="{{ vm.shareText }}"
socialshare-url="{{ vm.shareLink }}">
<ng-md-icon icon="facebook" aria-label="Facebook"></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.share-via' | translate:{provider:'Facebook'} }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button md-raised md-primary"
socialshare
socialshare-provider="twitter"
socialshare-text="{{ vm.shareTitle }}"
socialshare-hashtags="{{ vm.shareHashTags }}"
socialshare-url="{{ vm.shareLink }}">
<ng-md-icon icon="twitter" aria-label="Twitter"></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.share-via' | translate:{provider:'Twitter'} }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button md-raised md-primary"
socialshare
socialshare-provider="linkedin"
socialshare-text="{{ vm.shareTitle }}"
socialshare-url="{{ vm.shareLink }}">
<ng-md-icon icon="linkedin" aria-label="Linkedin"></ng-md-icon>
<md-tooltip md-direction="top">
{{ 'action.share-via' | translate:{provider:'Linkedin'} }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button md-raised md-primary"
socialshare
socialshare-provider="reddit"
socialshare-text="{{ vm.shareTitle }}"
socialshare-url="{{ vm.shareLink }}">
<md-icon md-svg-icon="mdi:reddit" aria-label="Reddit"></md-icon>
<md-tooltip md-direction="top">
{{ 'action.share-via' | translate:{provider:'Reddit'} }}
</md-tooltip>
</md-button>
</div>

View File

@ -36,7 +36,14 @@
<label translate>dashboard.assignedToCustomer</label> <label translate>dashboard.assignedToCustomer</label>
<input ng-model="assignedCustomer.title" disabled> <input ng-model="assignedCustomer.title" disabled>
</md-input-container> </md-input-container>
<div layout="row" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')"> <div layout="column" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')">
<tb-social-share-panel style="padding-bottom: 10px;"
share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
share-link="{{ publicLink }}"
share-hash-tags="thingsboard, iot">
</tb-social-share-panel>
<div layout="row">
<md-input-container class="md-block" flex> <md-input-container class="md-block" flex>
<label translate>dashboard.public-link</label> <label translate>dashboard.public-link</label>
<input ng-model="publicLink" disabled> <input ng-model="publicLink" disabled>
@ -51,6 +58,7 @@
</md-tooltip> </md-tooltip>
</md-button> </md-button>
</div> </div>
</div>
<fieldset ng-disabled="loading || !isEdit"> <fieldset ng-disabled="loading || !isEdit">
<md-input-container class="md-block"> <md-input-container class="md-block">
<label translate>dashboard.title</label> <label translate>dashboard.title</label>

View File

@ -31,6 +31,18 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
vm.gridSettings.showTitle = true; vm.gridSettings.showTitle = true;
} }
if (angular.isUndefined(vm.gridSettings.showDevicesSelect)) {
vm.gridSettings.showDevicesSelect = true;
}
if (angular.isUndefined(vm.gridSettings.showDashboardTimewindow)) {
vm.gridSettings.showDashboardTimewindow = true;
}
if (angular.isUndefined(vm.gridSettings.showDashboardExport)) {
vm.gridSettings.showDashboardExport = true;
}
vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)'; vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
vm.gridSettings.titleColor = vm.gridSettings.titleColor || 'rgba(0,0,0,0.870588)'; vm.gridSettings.titleColor = vm.gridSettings.titleColor || 'rgba(0,0,0,0.870588)';
vm.gridSettings.columns = vm.gridSettings.columns || 24; vm.gridSettings.columns = vm.gridSettings.columns || 24;

View File

@ -48,6 +48,17 @@
md-color-history="false" md-color-history="false"
></div> ></div>
</div> </div>
<div layout="row" layout-align="start center">
<md-checkbox flex aria-label="{{ 'dashboard.display-device-selection' | translate }}"
ng-model="vm.gridSettings.showDevicesSelect">{{ 'dashboard.display-device-selection' | translate }}
</md-checkbox>
<md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-timewindow' | translate }}"
ng-model="vm.gridSettings.showDashboardTimewindow">{{ 'dashboard.display-dashboard-timewindow' | translate }}
</md-checkbox>
<md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-export' | translate }}"
ng-model="vm.gridSettings.showDashboardExport">{{ 'dashboard.display-dashboard-export' | translate }}
</md-checkbox>
</div>
<md-input-container class="md-block"> <md-input-container class="md-block">
<label translate>dashboard.columns-count</label> <label translate>dashboard.columns-count</label>
<input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10" <input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"

View File

@ -48,6 +48,8 @@ export default function DashboardController(types, widgetService, userService,
vm.isToolbarOpened = false; vm.isToolbarOpened = false;
vm.thingsboardVersion = THINGSBOARD_VERSION; //eslint-disable-line
vm.currentDashboardId = $stateParams.dashboardId; vm.currentDashboardId = $stateParams.dashboardId;
if ($stateParams.customerId) { if ($stateParams.customerId) {
vm.currentCustomerId = $stateParams.customerId; vm.currentCustomerId = $stateParams.customerId;
@ -105,6 +107,9 @@ export default function DashboardController(types, widgetService, userService,
vm.onRevertWidgetEdit = onRevertWidgetEdit; vm.onRevertWidgetEdit = onRevertWidgetEdit;
vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType; vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
vm.displayTitle = displayTitle; vm.displayTitle = displayTitle;
vm.displayExport = displayExport;
vm.displayDashboardTimewindow = displayDashboardTimewindow;
vm.displayDevicesSelect = displayDevicesSelect;
vm.widgetsBundle; vm.widgetsBundle;
@ -565,6 +570,33 @@ export default function DashboardController(types, widgetService, userService,
} }
} }
function displayExport() {
if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
angular.isDefined(vm.dashboard.configuration.gridSettings.showDashboardExport)) {
return vm.dashboard.configuration.gridSettings.showDashboardExport;
} else {
return true;
}
}
function displayDashboardTimewindow() {
if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
angular.isDefined(vm.dashboard.configuration.gridSettings.showDashboardTimewindow)) {
return vm.dashboard.configuration.gridSettings.showDashboardTimewindow;
} else {
return true;
}
}
function displayDevicesSelect() {
if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
angular.isDefined(vm.dashboard.configuration.gridSettings.showDevicesSelect)) {
return vm.dashboard.configuration.gridSettings.showDevicesSelect;
} else {
return true;
}
}
function onRevertWidgetEdit(widgetForm) { function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) { if (widgetForm.$dirty) {
widgetForm.$setPristine(); widgetForm.$setPristine();

View File

@ -49,16 +49,21 @@
</md-button> </md-button>
<tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true"> <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true">
</tb-user-menu> </tb-user-menu>
<md-button aria-label="{{ 'action.export' | translate }}" class="md-icon-button" <md-button ng-show="vm.isEdit || vm.displayExport()"
aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
ng-click="vm.exportDashboard($event)"> ng-click="vm.exportDashboard($event)">
<md-tooltip md-direction="bottom"> <md-tooltip md-direction="bottom">
{{ 'dashboard.export' | translate }} {{ 'dashboard.export' | translate }}
</md-tooltip> </md-tooltip>
<md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon> <md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
</md-button> </md-button>
<tb-timewindow is-toolbar direction="left" tooltip-direction="bottom" aggregation ng-model="vm.dashboardConfiguration.timewindow"> <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
is-toolbar
direction="left"
tooltip-direction="bottom" aggregation
ng-model="vm.dashboardConfiguration.timewindow">
</tb-timewindow> </tb-timewindow>
<tb-aliases-device-select ng-show="!vm.isEdit" <tb-aliases-device-select ng-show="!vm.isEdit && vm.displayDevicesSelect()"
tooltip-direction="bottom" tooltip-direction="bottom"
ng-model="vm.aliasesInfo.deviceAliases" ng-model="vm.aliasesInfo.deviceAliases"
device-aliases-info="vm.aliasesInfo.deviceAliasesInfo"> device-aliases-info="vm.aliasesInfo.deviceAliasesInfo">
@ -304,6 +309,6 @@
</section> </section>
</section> </section>
<section class="tb-powered-by-footer" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}"> <section class="tb-powered-by-footer" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">
<span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard</a></span> <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ vm.thingsboardVersion }}</a></span>
</section> </section>
</md-content> </md-content>

View File

@ -224,7 +224,7 @@ export function DashboardsController(userService, dashboardService, customerServ
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, true); unassignFromCustomer($event, item, true);
}, },
name: function() { return $translate.instant('action.unshare') }, name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('dashboard.make-private') }, details: function() { return $translate.instant('dashboard.make-private') },
icon: "reply", icon: "reply",
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
@ -329,7 +329,7 @@ export function DashboardsController(userService, dashboardService, customerServ
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, true); unassignFromCustomer($event, item, true);
}, },
name: function() { return $translate.instant('action.unshare') }, name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('dashboard.make-private') }, details: function() { return $translate.instant('dashboard.make-private') },
icon: "reply", icon: "reply",
isEnabled: function(dashboard) { isEnabled: function(dashboard) {
@ -404,7 +404,28 @@ export function DashboardsController(userService, dashboardService, customerServ
} }
function saveDashboard(dashboard) { function saveDashboard(dashboard) {
return dashboardService.saveDashboard(dashboard); var deferred = $q.defer();
dashboardService.saveDashboard(dashboard).then(
function success(savedDashboard) {
var dashboards = [ savedDashboard ];
customerService.applyAssignedCustomersInfo(dashboards).then(
function success(items) {
if (items && items.length == 1) {
deferred.resolve(items[0]);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
} }
function assignToCustomer($event, dashboardIds) { function assignToCustomer($event, dashboardIds) {

View File

@ -30,6 +30,7 @@ import thingsboardDashboardSelect from '../components/dashboard-select.directive
import thingsboardDashboard from '../components/dashboard.directive'; import thingsboardDashboard from '../components/dashboard.directive';
import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive'; import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive'; import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
import thingsboardSocialsharePanel from '../components/socialshare-panel.directive';
import thingsboardTypes from '../common/types.constant'; import thingsboardTypes from '../common/types.constant';
import thingsboardItemBuffer from '../services/item-buffer.service'; import thingsboardItemBuffer from '../services/item-buffer.service';
import thingsboardImportExport from '../import-export'; import thingsboardImportExport from '../import-export';
@ -64,7 +65,8 @@ export default angular.module('thingsboard.dashboard', [
thingsboardDashboardSelect, thingsboardDashboardSelect,
thingsboardDashboard, thingsboardDashboard,
thingsboardExpandFullscreen, thingsboardExpandFullscreen,
thingsboardWidgetsBundleSelect thingsboardWidgetsBundleSelect,
thingsboardSocialsharePanel
]) ])
.config(DashboardRoutes) .config(DashboardRoutes)
.controller('DashboardsController', DashboardsController) .controller('DashboardsController', DashboardsController)

View File

@ -43,6 +43,12 @@
</md-button> </md-button>
</div> </div>
<div class="tb-notice" translate>dashboard.public-dashboard-notice</div> <div class="tb-notice" translate>dashboard.public-dashboard-notice</div>
<tb-social-share-panel style="padding-top: 15px;"
share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle:vm.dashboard.title} }}"
share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle:vm.dashboard.title} }}"
share-link="{{ vm.publicLink }}"
share-hash-tags="thingsboard, iot">
</tb-social-share-panel>
</md-content> </md-content>
</div> </div>
</md-dialog-content> </md-dialog-content>

View File

@ -185,7 +185,7 @@ export function DeviceController(userService, deviceService, customerService, $s
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, true); unassignFromCustomer($event, item, true);
}, },
name: function() { return $translate.instant('action.unshare') }, name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('device.make-private') }, details: function() { return $translate.instant('device.make-private') },
icon: "reply", icon: "reply",
isEnabled: function(device) { isEnabled: function(device) {
@ -271,7 +271,7 @@ export function DeviceController(userService, deviceService, customerService, $s
onAction: function ($event, item) { onAction: function ($event, item) {
unassignFromCustomer($event, item, true); unassignFromCustomer($event, item, true);
}, },
name: function() { return $translate.instant('action.unshare') }, name: function() { return $translate.instant('action.make-private') },
details: function() { return $translate.instant('device.make-private') }, details: function() { return $translate.instant('device.make-private') },
icon: "reply", icon: "reply",
isEnabled: function(device) { isEnabled: function(device) {
@ -364,8 +364,29 @@ export function DeviceController(userService, deviceService, customerService, $s
return device ? device.name : ''; return device ? device.name : '';
} }
function saveDevice (device) { function saveDevice(device) {
return deviceService.saveDevice(device); var deferred = $q.defer();
deviceService.saveDevice(device).then(
function success(savedDevice) {
var devices = [ savedDevice ];
customerService.applyAssignedCustomersInfo(devices).then(
function success(items) {
if (items && items.length == 1) {
deferred.resolve(items[0]);
} else {
deferred.reject();
}
},
function fail() {
deferred.reject();
}
);
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
} }
function isCustomerUser() { function isCustomerUser() {

View File

@ -44,7 +44,7 @@ export default angular.module('thingsboard.locale', [])
"assign": "Assign", "assign": "Assign",
"unassign": "Unassign", "unassign": "Unassign",
"share": "Share", "share": "Share",
"unshare": "Unshare", "make-private": "Make private",
"apply": "Apply", "apply": "Apply",
"apply-changes": "Apply changes", "apply-changes": "Apply changes",
"edit-mode": "Edit mode", "edit-mode": "Edit mode",
@ -63,7 +63,8 @@ export default angular.module('thingsboard.locale', [])
"copy": "Copy", "copy": "Copy",
"paste": "Paste", "paste": "Paste",
"import": "Import", "import": "Import",
"export": "Export" "export": "Export",
"share-via": "Share via {{provider}}"
}, },
"aggregation": { "aggregation": {
"aggregation": "Aggregation", "aggregation": "Aggregation",
@ -233,6 +234,8 @@ export default angular.module('thingsboard.locale', [])
"make-private-dashboard-title": "Are you sure you want to make the dashboard '{{dashboardTitle}}' private?", "make-private-dashboard-title": "Are you sure you want to make the dashboard '{{dashboardTitle}}' private?",
"make-private-dashboard-text": "After the confirmation the dashboard will be made private and won't be accessible by others.", "make-private-dashboard-text": "After the confirmation the dashboard will be made private and won't be accessible by others.",
"make-private-dashboard": "Make dashboard private", "make-private-dashboard": "Make dashboard private",
"socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
"socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
"select-dashboard": "Select dashboard", "select-dashboard": "Select dashboard",
"no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.", "no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
"dashboard-required": "Dashboard is required.", "dashboard-required": "Dashboard is required.",
@ -262,6 +265,9 @@ export default angular.module('thingsboard.locale', [])
"max-vertical-margin-message": "Only 50 is allowed as maximum 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",
"title-color": "Title color", "title-color": "Title color",
"display-device-selection": "Display device selection",
"display-dashboard-timewindow": "Display timewindow",
"display-dashboard-export": "Display export",
"import": "Import dashboard", "import": "Import dashboard",
"export": "Export dashboard", "export": "Export dashboard",
"export-failed-error": "Unable to export dashboard: {{error}}", "export-failed-error": "Unable to export dashboard: {{error}}",

View File

@ -60,6 +60,7 @@ module.exports = {
allChunks: true, allChunks: true,
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
THINGSBOARD_VERSION: JSON.stringify(require('./package.json').version),
'__DEVTOOLS__': false, '__DEVTOOLS__': false,
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify('development'), NODE_ENV: JSON.stringify('development'),

View File

@ -58,6 +58,7 @@ module.exports = {
allChunks: true, allChunks: true,
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
THINGSBOARD_VERSION: JSON.stringify(require('./package.json').version),
'__DEVTOOLS__': false, '__DEVTOOLS__': false,
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify('production'), NODE_ENV: JSON.stringify('production'),