Commit bfd654d84fe4a270a76e815eb8eb428cde02e62e

Authored by Igor Kulikov
1 parent 912c572a

Add social share buttons for public dashboards sharing. Improve dashboard toolba…

…r configuration. Add project version information to bottom right part of the dashboard.
... ... @@ -33,6 +33,7 @@
33 33 "angular-messages": "1.5.8",
34 34 "angular-route": "1.5.8",
35 35 "angular-sanitize": "1.5.8",
  36 + "angular-socialshare": "^2.3.8",
36 37 "angular-storage": "0.0.15",
37 38 "angular-touch": "1.5.8",
38 39 "angular-translate": "2.13.1",
... ...
... ... @@ -19,6 +19,7 @@ import angular from 'angular';
19 19 import ngMaterial from 'angular-material';
20 20 import ngMdIcons from 'angular-material-icons';
21 21 import ngCookies from 'angular-cookies';
  22 +import angularSocialshare from 'angular-socialshare';
22 23 import 'angular-translate';
23 24 import 'angular-translate-loader-static-files';
24 25 import 'angular-translate-storage-local';
... ... @@ -82,6 +83,7 @@ angular.module('thingsboard', [
82 83 ngMaterial,
83 84 ngMdIcons,
84 85 ngCookies,
  86 + angularSocialshare,
85 87 'pascalprecht.translate',
86 88 'mdColorPicker',
87 89 mdPickers,
... ...
... ... @@ -106,7 +106,8 @@ function Utils($mdColorPalette, $rootScope, $window, $q, deviceService, types) {
106 106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
107 107 filterSearchTextEntities: filterSearchTextEntities,
108 108 guid: guid,
109   - createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo
  109 + createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
  110 + isLocalUrl: isLocalUrl
110 111 }
111 112
112 113 return service;
... ... @@ -428,4 +429,15 @@ function Utils($mdColorPalette, $rootScope, $window, $q, deviceService, types) {
428 429 return deferred.promise;
429 430 }
430 431
  432 + function isLocalUrl(url) {
  433 + var parser = document.createElement('a'); //eslint-disable-line
  434 + parser.href = url;
  435 + var host = parser.hostname;
  436 + if (host === "localhost" || host === "127.0.0.1") {
  437 + return true;
  438 + } else {
  439 + return false;
  440 + }
  441 + }
  442 +
431 443 }
... ...
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import socialsharePanelTemplate from './socialshare-panel.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +
  24 +export default angular.module('thingsboard.directives.socialsharePanel', [])
  25 + .directive('tbSocialSharePanel', SocialsharePanel)
  26 + .name;
  27 +
  28 +/*@ngInject*/
  29 +function SocialsharePanel() {
  30 + return {
  31 + restrict: "E",
  32 + scope: true,
  33 + bindToController: {
  34 + shareTitle: '@',
  35 + shareText: '@',
  36 + shareLink: '@',
  37 + shareHashTags: '@'
  38 + },
  39 + controller: SocialsharePanelController,
  40 + controllerAs: 'vm',
  41 + templateUrl: socialsharePanelTemplate
  42 + };
  43 +}
  44 +
  45 +/*@ngInject*/
  46 +function SocialsharePanelController(utils) {
  47 +
  48 + let vm = this;
  49 +
  50 + vm.isShareLinkLocal = function() {
  51 + if (vm.shareLink && vm.shareLink.length > 0) {
  52 + return utils.isLocalUrl(vm.shareLink);
  53 + } else {
  54 + return true;
  55 + }
  56 + }
  57 +
  58 +}
\ No newline at end of file
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<div layout="row" ng-show="!vm.isShareLinkLocal()">
  20 + <md-button class="md-icon-button md-raised md-primary"
  21 + socialshare
  22 + socialshare-provider="facebook"
  23 + socialshare-title="{{ vm.shareTitle }}"
  24 + socialshare-text="{{ vm.shareText }}"
  25 + socialshare-url="{{ vm.shareLink }}">
  26 + <ng-md-icon icon="facebook" aria-label="Facebook"></ng-md-icon>
  27 + <md-tooltip md-direction="top">
  28 + {{ 'action.share-via' | translate:{provider:'Facebook'} }}
  29 + </md-tooltip>
  30 + </md-button>
  31 + <md-button class="md-icon-button md-raised md-primary"
  32 + socialshare
  33 + socialshare-provider="twitter"
  34 + socialshare-text="{{ vm.shareTitle }}"
  35 + socialshare-hashtags="{{ vm.shareHashTags }}"
  36 + socialshare-url="{{ vm.shareLink }}">
  37 + <ng-md-icon icon="twitter" aria-label="Twitter"></ng-md-icon>
  38 + <md-tooltip md-direction="top">
  39 + {{ 'action.share-via' | translate:{provider:'Twitter'} }}
  40 + </md-tooltip>
  41 + </md-button>
  42 + <md-button class="md-icon-button md-raised md-primary"
  43 + socialshare
  44 + socialshare-provider="linkedin"
  45 + socialshare-text="{{ vm.shareTitle }}"
  46 + socialshare-url="{{ vm.shareLink }}">
  47 + <ng-md-icon icon="linkedin" aria-label="Linkedin"></ng-md-icon>
  48 + <md-tooltip md-direction="top">
  49 + {{ 'action.share-via' | translate:{provider:'Linkedin'} }}
  50 + </md-tooltip>
  51 + </md-button>
  52 + <md-button class="md-icon-button md-raised md-primary"
  53 + socialshare
  54 + socialshare-provider="reddit"
  55 + socialshare-text="{{ vm.shareTitle }}"
  56 + socialshare-url="{{ vm.shareLink }}">
  57 + <md-icon md-svg-icon="mdi:reddit" aria-label="Reddit"></md-icon>
  58 + <md-tooltip md-direction="top">
  59 + {{ 'action.share-via' | translate:{provider:'Reddit'} }}
  60 + </md-tooltip>
  61 + </md-button>
  62 +</div>
\ No newline at end of file
... ...
... ... @@ -36,20 +36,28 @@
36 36 <label translate>dashboard.assignedToCustomer</label>
37 37 <input ng-model="assignedCustomer.title" disabled>
38 38 </md-input-container>
39   - <div layout="row" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')">
40   - <md-input-container class="md-block" flex>
41   - <label translate>dashboard.public-link</label>
42   - <input ng-model="publicLink" disabled>
43   - </md-input-container>
44   - <md-button class="md-icon-button" style="margin-top: 14px;"
45   - ngclipboard
46   - data-clipboard-text="{{ publicLink }}"
47   - ngclipboard-success="onPublicLinkCopied(e)">
48   - <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
49   - <md-tooltip md-direction="top">
50   - {{ 'dashboard.copy-public-link' | translate }}
51   - </md-tooltip>
52   - </md-button>
  39 + <div layout="column" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')">
  40 + <tb-social-share-panel style="padding-bottom: 10px;"
  41 + share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
  42 + share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
  43 + share-link="{{ publicLink }}"
  44 + share-hash-tags="thingsboard, iot">
  45 + </tb-social-share-panel>
  46 + <div layout="row">
  47 + <md-input-container class="md-block" flex>
  48 + <label translate>dashboard.public-link</label>
  49 + <input ng-model="publicLink" disabled>
  50 + </md-input-container>
  51 + <md-button class="md-icon-button" style="margin-top: 14px;"
  52 + ngclipboard
  53 + data-clipboard-text="{{ publicLink }}"
  54 + ngclipboard-success="onPublicLinkCopied(e)">
  55 + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
  56 + <md-tooltip md-direction="top">
  57 + {{ 'dashboard.copy-public-link' | translate }}
  58 + </md-tooltip>
  59 + </md-button>
  60 + </div>
53 61 </div>
54 62 <fieldset ng-disabled="loading || !isEdit">
55 63 <md-input-container class="md-block">
... ...
... ... @@ -31,6 +31,18 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
31 31 vm.gridSettings.showTitle = true;
32 32 }
33 33
  34 + if (angular.isUndefined(vm.gridSettings.showDevicesSelect)) {
  35 + vm.gridSettings.showDevicesSelect = true;
  36 + }
  37 +
  38 + if (angular.isUndefined(vm.gridSettings.showDashboardTimewindow)) {
  39 + vm.gridSettings.showDashboardTimewindow = true;
  40 + }
  41 +
  42 + if (angular.isUndefined(vm.gridSettings.showDashboardExport)) {
  43 + vm.gridSettings.showDashboardExport = true;
  44 + }
  45 +
34 46 vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
35 47 vm.gridSettings.titleColor = vm.gridSettings.titleColor || 'rgba(0,0,0,0.870588)';
36 48 vm.gridSettings.columns = vm.gridSettings.columns || 24;
... ...
... ... @@ -48,6 +48,17 @@
48 48 md-color-history="false"
49 49 ></div>
50 50 </div>
  51 + <div layout="row" layout-align="start center">
  52 + <md-checkbox flex aria-label="{{ 'dashboard.display-device-selection' | translate }}"
  53 + ng-model="vm.gridSettings.showDevicesSelect">{{ 'dashboard.display-device-selection' | translate }}
  54 + </md-checkbox>
  55 + <md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-timewindow' | translate }}"
  56 + ng-model="vm.gridSettings.showDashboardTimewindow">{{ 'dashboard.display-dashboard-timewindow' | translate }}
  57 + </md-checkbox>
  58 + <md-checkbox flex aria-label="{{ 'dashboard.display-dashboard-export' | translate }}"
  59 + ng-model="vm.gridSettings.showDashboardExport">{{ 'dashboard.display-dashboard-export' | translate }}
  60 + </md-checkbox>
  61 + </div>
51 62 <md-input-container class="md-block">
52 63 <label translate>dashboard.columns-count</label>
53 64 <input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"
... ...
... ... @@ -48,6 +48,8 @@ export default function DashboardController(types, widgetService, userService,
48 48
49 49 vm.isToolbarOpened = false;
50 50
  51 + vm.thingsboardVersion = THINGSBOARD_VERSION; //eslint-disable-line
  52 +
51 53 vm.currentDashboardId = $stateParams.dashboardId;
52 54 if ($stateParams.customerId) {
53 55 vm.currentCustomerId = $stateParams.customerId;
... ... @@ -105,6 +107,9 @@ export default function DashboardController(types, widgetService, userService,
105 107 vm.onRevertWidgetEdit = onRevertWidgetEdit;
106 108 vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
107 109 vm.displayTitle = displayTitle;
  110 + vm.displayExport = displayExport;
  111 + vm.displayDashboardTimewindow = displayDashboardTimewindow;
  112 + vm.displayDevicesSelect = displayDevicesSelect;
108 113
109 114 vm.widgetsBundle;
110 115
... ... @@ -565,6 +570,33 @@ export default function DashboardController(types, widgetService, userService,
565 570 }
566 571 }
567 572
  573 + function displayExport() {
  574 + if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
  575 + angular.isDefined(vm.dashboard.configuration.gridSettings.showDashboardExport)) {
  576 + return vm.dashboard.configuration.gridSettings.showDashboardExport;
  577 + } else {
  578 + return true;
  579 + }
  580 + }
  581 +
  582 + function displayDashboardTimewindow() {
  583 + if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
  584 + angular.isDefined(vm.dashboard.configuration.gridSettings.showDashboardTimewindow)) {
  585 + return vm.dashboard.configuration.gridSettings.showDashboardTimewindow;
  586 + } else {
  587 + return true;
  588 + }
  589 + }
  590 +
  591 + function displayDevicesSelect() {
  592 + if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
  593 + angular.isDefined(vm.dashboard.configuration.gridSettings.showDevicesSelect)) {
  594 + return vm.dashboard.configuration.gridSettings.showDevicesSelect;
  595 + } else {
  596 + return true;
  597 + }
  598 + }
  599 +
568 600 function onRevertWidgetEdit(widgetForm) {
569 601 if (widgetForm.$dirty) {
570 602 widgetForm.$setPristine();
... ...
... ... @@ -49,16 +49,21 @@
49 49 </md-button>
50 50 <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true">
51 51 </tb-user-menu>
52   - <md-button aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
  52 + <md-button ng-show="vm.isEdit || vm.displayExport()"
  53 + aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
53 54 ng-click="vm.exportDashboard($event)">
54 55 <md-tooltip md-direction="bottom">
55 56 {{ 'dashboard.export' | translate }}
56 57 </md-tooltip>
57 58 <md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
58 59 </md-button>
59   - <tb-timewindow is-toolbar direction="left" tooltip-direction="bottom" aggregation ng-model="vm.dashboardConfiguration.timewindow">
  60 + <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
  61 + is-toolbar
  62 + direction="left"
  63 + tooltip-direction="bottom" aggregation
  64 + ng-model="vm.dashboardConfiguration.timewindow">
60 65 </tb-timewindow>
61   - <tb-aliases-device-select ng-show="!vm.isEdit"
  66 + <tb-aliases-device-select ng-show="!vm.isEdit && vm.displayDevicesSelect()"
62 67 tooltip-direction="bottom"
63 68 ng-model="vm.aliasesInfo.deviceAliases"
64 69 device-aliases-info="vm.aliasesInfo.deviceAliasesInfo">
... ... @@ -304,6 +309,6 @@
304 309 </section>
305 310 </section>
306 311 <section class="tb-powered-by-footer" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">
307   - <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard</a></span>
  312 + <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ vm.thingsboardVersion }}</a></span>
308 313 </section>
309 314 </md-content>
... ...
... ... @@ -224,7 +224,7 @@ export function DashboardsController(userService, dashboardService, customerServ
224 224 onAction: function ($event, item) {
225 225 unassignFromCustomer($event, item, true);
226 226 },
227   - name: function() { return $translate.instant('action.unshare') },
  227 + name: function() { return $translate.instant('action.make-private') },
228 228 details: function() { return $translate.instant('dashboard.make-private') },
229 229 icon: "reply",
230 230 isEnabled: function(dashboard) {
... ... @@ -329,7 +329,7 @@ export function DashboardsController(userService, dashboardService, customerServ
329 329 onAction: function ($event, item) {
330 330 unassignFromCustomer($event, item, true);
331 331 },
332   - name: function() { return $translate.instant('action.unshare') },
  332 + name: function() { return $translate.instant('action.make-private') },
333 333 details: function() { return $translate.instant('dashboard.make-private') },
334 334 icon: "reply",
335 335 isEnabled: function(dashboard) {
... ... @@ -404,7 +404,28 @@ export function DashboardsController(userService, dashboardService, customerServ
404 404 }
405 405
406 406 function saveDashboard(dashboard) {
407   - return dashboardService.saveDashboard(dashboard);
  407 + var deferred = $q.defer();
  408 + dashboardService.saveDashboard(dashboard).then(
  409 + function success(savedDashboard) {
  410 + var dashboards = [ savedDashboard ];
  411 + customerService.applyAssignedCustomersInfo(dashboards).then(
  412 + function success(items) {
  413 + if (items && items.length == 1) {
  414 + deferred.resolve(items[0]);
  415 + } else {
  416 + deferred.reject();
  417 + }
  418 + },
  419 + function fail() {
  420 + deferred.reject();
  421 + }
  422 + );
  423 + },
  424 + function fail() {
  425 + deferred.reject();
  426 + }
  427 + );
  428 + return deferred.promise;
408 429 }
409 430
410 431 function assignToCustomer($event, dashboardIds) {
... ...
... ... @@ -30,6 +30,7 @@ import thingsboardDashboardSelect from '../components/dashboard-select.directive
30 30 import thingsboardDashboard from '../components/dashboard.directive';
31 31 import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
32 32 import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
  33 +import thingsboardSocialsharePanel from '../components/socialshare-panel.directive';
33 34 import thingsboardTypes from '../common/types.constant';
34 35 import thingsboardItemBuffer from '../services/item-buffer.service';
35 36 import thingsboardImportExport from '../import-export';
... ... @@ -64,7 +65,8 @@ export default angular.module('thingsboard.dashboard', [
64 65 thingsboardDashboardSelect,
65 66 thingsboardDashboard,
66 67 thingsboardExpandFullscreen,
67   - thingsboardWidgetsBundleSelect
  68 + thingsboardWidgetsBundleSelect,
  69 + thingsboardSocialsharePanel
68 70 ])
69 71 .config(DashboardRoutes)
70 72 .controller('DashboardsController', DashboardsController)
... ...
... ... @@ -43,6 +43,12 @@
43 43 </md-button>
44 44 </div>
45 45 <div class="tb-notice" translate>dashboard.public-dashboard-notice</div>
  46 + <tb-social-share-panel style="padding-top: 15px;"
  47 + share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle:vm.dashboard.title} }}"
  48 + share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle:vm.dashboard.title} }}"
  49 + share-link="{{ vm.publicLink }}"
  50 + share-hash-tags="thingsboard, iot">
  51 + </tb-social-share-panel>
46 52 </md-content>
47 53 </div>
48 54 </md-dialog-content>
... ...
... ... @@ -185,7 +185,7 @@ export function DeviceController(userService, deviceService, customerService, $s
185 185 onAction: function ($event, item) {
186 186 unassignFromCustomer($event, item, true);
187 187 },
188   - name: function() { return $translate.instant('action.unshare') },
  188 + name: function() { return $translate.instant('action.make-private') },
189 189 details: function() { return $translate.instant('device.make-private') },
190 190 icon: "reply",
191 191 isEnabled: function(device) {
... ... @@ -271,7 +271,7 @@ export function DeviceController(userService, deviceService, customerService, $s
271 271 onAction: function ($event, item) {
272 272 unassignFromCustomer($event, item, true);
273 273 },
274   - name: function() { return $translate.instant('action.unshare') },
  274 + name: function() { return $translate.instant('action.make-private') },
275 275 details: function() { return $translate.instant('device.make-private') },
276 276 icon: "reply",
277 277 isEnabled: function(device) {
... ... @@ -364,8 +364,29 @@ export function DeviceController(userService, deviceService, customerService, $s
364 364 return device ? device.name : '';
365 365 }
366 366
367   - function saveDevice (device) {
368   - return deviceService.saveDevice(device);
  367 + function saveDevice(device) {
  368 + var deferred = $q.defer();
  369 + deviceService.saveDevice(device).then(
  370 + function success(savedDevice) {
  371 + var devices = [ savedDevice ];
  372 + customerService.applyAssignedCustomersInfo(devices).then(
  373 + function success(items) {
  374 + if (items && items.length == 1) {
  375 + deferred.resolve(items[0]);
  376 + } else {
  377 + deferred.reject();
  378 + }
  379 + },
  380 + function fail() {
  381 + deferred.reject();
  382 + }
  383 + );
  384 + },
  385 + function fail() {
  386 + deferred.reject();
  387 + }
  388 + );
  389 + return deferred.promise;
369 390 }
370 391
371 392 function isCustomerUser() {
... ...
... ... @@ -44,7 +44,7 @@ export default angular.module('thingsboard.locale', [])
44 44 "assign": "Assign",
45 45 "unassign": "Unassign",
46 46 "share": "Share",
47   - "unshare": "Unshare",
  47 + "make-private": "Make private",
48 48 "apply": "Apply",
49 49 "apply-changes": "Apply changes",
50 50 "edit-mode": "Edit mode",
... ... @@ -63,7 +63,8 @@ export default angular.module('thingsboard.locale', [])
63 63 "copy": "Copy",
64 64 "paste": "Paste",
65 65 "import": "Import",
66   - "export": "Export"
  66 + "export": "Export",
  67 + "share-via": "Share via {{provider}}"
67 68 },
68 69 "aggregation": {
69 70 "aggregation": "Aggregation",
... ... @@ -233,6 +234,8 @@ export default angular.module('thingsboard.locale', [])
233 234 "make-private-dashboard-title": "Are you sure you want to make the dashboard '{{dashboardTitle}}' private?",
234 235 "make-private-dashboard-text": "After the confirmation the dashboard will be made private and won't be accessible by others.",
235 236 "make-private-dashboard": "Make dashboard private",
  237 + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
  238 + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
236 239 "select-dashboard": "Select dashboard",
237 240 "no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
238 241 "dashboard-required": "Dashboard is required.",
... ... @@ -262,6 +265,9 @@ export default angular.module('thingsboard.locale', [])
262 265 "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
263 266 "display-title": "Display dashboard title",
264 267 "title-color": "Title color",
  268 + "display-device-selection": "Display device selection",
  269 + "display-dashboard-timewindow": "Display timewindow",
  270 + "display-dashboard-export": "Display export",
265 271 "import": "Import dashboard",
266 272 "export": "Export dashboard",
267 273 "export-failed-error": "Unable to export dashboard: {{error}}",
... ...
... ... @@ -60,6 +60,7 @@ module.exports = {
60 60 allChunks: true,
61 61 }),
62 62 new webpack.DefinePlugin({
  63 + THINGSBOARD_VERSION: JSON.stringify(require('./package.json').version),
63 64 '__DEVTOOLS__': false,
64 65 'process.env': {
65 66 NODE_ENV: JSON.stringify('development'),
... ...
... ... @@ -58,6 +58,7 @@ module.exports = {
58 58 allChunks: true,
59 59 }),
60 60 new webpack.DefinePlugin({
  61 + THINGSBOARD_VERSION: JSON.stringify(require('./package.json').version),
61 62 '__DEVTOOLS__': false,
62 63 'process.env': {
63 64 NODE_ENV: JSON.stringify('production'),
... ...