Commit 25ba81394dee5afe6d63e4c9db18a6a6d5036bae

Authored by Igor Kulikov
2 parents 95466c39 0ed07256

Merge branch 'master' of github.com:thingsboard/thingsboard into develop/3.0

@@ -29,6 +29,8 @@ import thingsboardWebCameraInputWidget from '../widget/lib/web-camera-input-widg @@ -29,6 +29,8 @@ import thingsboardWebCameraInputWidget from '../widget/lib/web-camera-input-widg
29 29
30 import thingsboardRpcWidgets from '../widget/lib/rpc'; 30 import thingsboardRpcWidgets from '../widget/lib/rpc';
31 31
  32 +import thingsboardJsonToString from '../components/tb-json-to-string.directive';
  33 +
32 import TbFlot from '../widget/lib/flot-widget'; 34 import TbFlot from '../widget/lib/flot-widget';
33 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; 35 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
34 import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge'; 36 import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
@@ -52,7 +54,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo @@ -52,7 +54,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo
52 thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, 54 thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget,
53 thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, 55 thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget,
54 thingsboardMultipleInputWidget, thingsboardWebCameraInputWidget, thingsboardRpcWidgets, thingsboardTypes, 56 thingsboardMultipleInputWidget, thingsboardWebCameraInputWidget, thingsboardRpcWidgets, thingsboardTypes,
55 - thingsboardUtils, TripAnimationWidget]) 57 + thingsboardUtils, thingsboardJsonToString, TripAnimationWidget])
56 .factory('widgetService', WidgetService) 58 .factory('widgetService', WidgetService)
57 .name; 59 .name;
58 60
@@ -881,6 +881,11 @@ export default angular.module('thingsboard.types', []) @@ -881,6 +881,11 @@ export default angular.module('thingsboard.types', [])
881 value: "boolean", 881 value: "boolean",
882 name: "value.boolean", 882 name: "value.boolean",
883 icon: "mdi:checkbox-marked-outline" 883 icon: "mdi:checkbox-marked-outline"
  884 + },
  885 + json: {
  886 + value: "json",
  887 + name: "value.json",
  888 + icon: "mdi:json"
884 } 889 }
885 }, 890 },
886 widgetType: { 891 widgetType: {
@@ -57,9 +57,12 @@ function JsonContent($compile, $templateCache, toast, types, utils) { @@ -57,9 +57,12 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
57 updateEditorSize(); 57 updateEditorSize();
58 }; 58 };
59 59
60 - scope.beautifyJson = function () {  
61 - var res = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60});  
62 - scope.contentBody = res; 60 + scope.beautifyJSON = function () {
  61 + scope.contentBody = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60});
  62 + };
  63 +
  64 + scope.minifyJSON = function () {
  65 + scope.contentBody = angular.toJson(angular.fromJson(scope.contentBody));
63 }; 66 };
64 67
65 function updateEditorSize() { 68 function updateEditorSize() {
@@ -116,7 +119,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) { @@ -116,7 +119,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
116 scope.$watch('contentBody', function (newContent, oldContent) { 119 scope.$watch('contentBody', function (newContent, oldContent) {
117 ngModelCtrl.$setViewValue(scope.contentBody); 120 ngModelCtrl.$setViewValue(scope.contentBody);
118 if (!angular.equals(newContent, oldContent)) { 121 if (!angular.equals(newContent, oldContent)) {
119 - scope.contentValid = true; 122 + scope.contentValid = scope.validate();
120 } 123 }
121 scope.updateValidity(); 124 scope.updateValidity();
122 }); 125 });
@@ -139,15 +142,17 @@ function JsonContent($compile, $templateCache, toast, types, utils) { @@ -139,15 +142,17 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
139 } 142 }
140 return true; 143 return true;
141 } catch (e) { 144 } catch (e) {
142 - var details = utils.parseException(e);  
143 - var errorInfo = 'Error:';  
144 - if (details.name) {  
145 - errorInfo += ' ' + details.name + ':';  
146 - }  
147 - if (details.message) {  
148 - errorInfo += ' ' + details.message; 145 + if (!scope.hideErrorToast) {
  146 + var details = utils.parseException(e);
  147 + var errorInfo = 'Error:';
  148 + if (details.name) {
  149 + errorInfo += ' ' + details.name + ':';
  150 + }
  151 + if (details.message) {
  152 + errorInfo += ' ' + details.message;
  153 + }
  154 + scope.showError(errorInfo);
149 } 155 }
150 - scope.showError(errorInfo);  
151 return false; 156 return false;
152 } 157 }
153 }; 158 };
@@ -169,7 +174,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) { @@ -169,7 +174,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
169 }); 174 });
170 175
171 $compile(element.contents())(scope); 176 $compile(element.contents())(scope);
172 - } 177 + };
173 178
174 return { 179 return {
175 restrict: "E", 180 restrict: "E",
@@ -177,6 +182,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) { @@ -177,6 +182,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) {
177 scope: { 182 scope: {
178 contentType: '=', 183 contentType: '=',
179 validateContent: '=?', 184 validateContent: '=?',
  185 + hideErrorToast: '=?',
180 readonly:'=ngReadonly', 186 readonly:'=ngReadonly',
181 fillHeight:'=?' 187 fillHeight:'=?'
182 }, 188 },
@@ -27,7 +27,7 @@ tb-json-content { @@ -27,7 +27,7 @@ tb-json-content {
27 min-height: 15px; 27 min-height: 15px;
28 padding: 4px; 28 padding: 4px;
29 margin: 0 5px 0 0; 29 margin: 0 5px 0 0;
30 - font-size: .8rem; 30 + font-size: 12px;
31 line-height: 15px; 31 line-height: 15px;
32 color: #7b7b7b; 32 color: #7b7b7b;
33 background: rgba(220, 220, 220, .35); 33 background: rgba(220, 220, 220, .35);
@@ -17,11 +17,15 @@ @@ -17,11 +17,15 @@
17 --> 17 -->
18 <div style="background: #fff;" ng-class="{'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> 18 <div style="background: #fff;" ng-class="{'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
19 <div layout="row" layout-align="start center" style="height: 40px;" class="tb-json-content-toolbar"> 19 <div layout="row" layout-align="start center" style="height: 40px;" class="tb-json-content-toolbar">
20 - <label class="tb-title no-padding">{{ label }}</label> 20 + <label class="tb-title no-padding"
  21 + ng-class="{'tb-error': !contentValid}">{{ label }}</label>
21 <span flex></span> 22 <span flex></span>
22 - <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJson()">{{ 23 + <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJSON()">{{
23 'js-func.tidy' | translate }} 24 'js-func.tidy' | translate }}
24 </md-button> 25 </md-button>
  26 + <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.mini' | translate }}" ng-click="minifyJSON()">{{
  27 + 'js-func.mini' | translate }}
  28 + </md-button>
25 <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button> 29 <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
26 </div> 30 </div>
27 <div flex id="tb-json-panel" class="tb-json-content-panel" layout="column"> 31 <div flex id="tb-json-panel" class="tb-json-content-panel" layout="column">
@@ -50,6 +50,14 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { @@ -50,6 +50,14 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) {
50 updateEditorSize(); 50 updateEditorSize();
51 }; 51 };
52 52
  53 + scope.beautifyJSON = function () {
  54 + scope.contentBody = angular.toJson(scope.object, 4);
  55 + };
  56 +
  57 + scope.minifyJSON = function () {
  58 + scope.contentBody = angular.toJson(scope.object);
  59 + };
  60 +
53 function updateEditorSize() { 61 function updateEditorSize() {
54 if (scope.json_editor) { 62 if (scope.json_editor) {
55 scope.json_editor.resize(); 63 scope.json_editor.resize();
@@ -169,7 +177,7 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { @@ -169,7 +177,7 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) {
169 }); 177 });
170 178
171 $compile(element.contents())(scope); 179 $compile(element.contents())(scope);
172 - } 180 + };
173 181
174 return { 182 return {
175 restrict: "E", 183 restrict: "E",
@@ -21,6 +21,19 @@ tb-json-object-edit { @@ -21,6 +21,19 @@ tb-json-object-edit {
21 } 21 }
22 } 22 }
23 23
  24 +.tb-json-object-edit-toolbar {
  25 + .md-button.tidy {
  26 + min-width: 32px;
  27 + min-height: 15px;
  28 + padding: 4px;
  29 + margin: 0 5px 0 0;
  30 + font-size: 12px;
  31 + line-height: 15px;
  32 + color: #7b7b7b;
  33 + background: rgba(220, 220, 220, .35);
  34 + }
  35 +}
  36 +
24 .tb-json-object-panel { 37 .tb-json-object-panel {
25 height: 100%; 38 height: 100%;
26 margin-left: 15px; 39 margin-left: 15px;
@@ -16,12 +16,18 @@ @@ -16,12 +16,18 @@
16 16
17 --> 17 -->
18 <div style="background: #fff;" ng-class="{'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> 18 <div style="background: #fff;" ng-class="{'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
19 - <div layout="row" layout-align="start center"> 19 + <div layout="row" layout-align="start center" class="tb-json-object-edit-toolbar">
20 <label class="tb-title no-padding" 20 <label class="tb-title no-padding"
21 ng-class="{'tb-required': required, 21 ng-class="{'tb-required': required,
22 'tb-readonly': readonly, 22 'tb-readonly': readonly,
23 'tb-error': !objectValid}">{{ label }}</label> 23 'tb-error': !objectValid}">{{ label }}</label>
24 <span flex></span> 24 <span flex></span>
  25 + <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.tidy' | translate }}" ng-click="beautifyJSON()">
  26 + {{'js-func.tidy' | translate }}
  27 + </md-button>
  28 + <md-button ng-if="!readonly" class="tidy" aria-label="{{ 'js-func.mini' | translate }}" ng-click="minifyJSON()">
  29 + {{'js-func.mini' | translate }}
  30 + </md-button>
25 <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button> 31 <md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
26 </div> 32 </div>
27 <div flex id="tb-json-panel" class="tb-json-object-panel" layout="column"> 33 <div flex id="tb-json-panel" class="tb-json-object-panel" layout="column">
  1 +/*
  2 + * Copyright © 2016-2020 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 +export default angular.module('tbJsonToString', [])
  17 + .directive('tbJsonToString', InputJson)
  18 + .name;
  19 +
  20 +function InputJson() {
  21 + return {
  22 + restrict: 'A',
  23 + require: 'ngModel',
  24 + link: function(scope, element, attr, ngModelCtrl) {
  25 + function into(input) {
  26 + try {
  27 + ngModelCtrl.$setValidity('invalidJSON', true);
  28 + return angular.fromJson(input);
  29 + } catch (e) {
  30 + ngModelCtrl.$setValidity('invalidJSON', false);
  31 + }
  32 + }
  33 + function out(data) {
  34 + try {
  35 + ngModelCtrl.$setValidity('invalidJSON', true);
  36 + return angular.toJson(data);
  37 + } catch (e) {
  38 + ngModelCtrl.$setValidity('invalidJSON', false);
  39 + }
  40 + }
  41 + ngModelCtrl.$parsers.push(into);
  42 + ngModelCtrl.$formatters.push(out);
  43 + }
  44 + };
  45 +}
@@ -13,10 +13,18 @@ @@ -13,10 +13,18 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
  16 +/* eslint-disable import/no-unresolved, import/default */
  17 +
  18 +import attributeDialogEditJsonTemplate from './attribute-dialog-edit-json.tpl.html';
  19 +
  20 +/* eslint-enable import/no-unresolved, import/default */
  21 +
  22 +import AttributeDialogEditJsonController from './attribute-dialog-edit-json.controller';
  23 +
16 /*@ngInject*/ 24 /*@ngInject*/
17 export default function AddAttributeDialogController($scope, $mdDialog, types, attributeService, entityType, entityId, attributeScope) { 25 export default function AddAttributeDialogController($scope, $mdDialog, types, attributeService, entityType, entityId, attributeScope) {
18 26
19 - var vm = this; 27 + let vm = this;
20 28
21 vm.attribute = {}; 29 vm.attribute = {};
22 30
@@ -40,11 +48,37 @@ export default function AddAttributeDialogController($scope, $mdDialog, types, a @@ -40,11 +48,37 @@ export default function AddAttributeDialogController($scope, $mdDialog, types, a
40 ); 48 );
41 } 49 }
42 50
43 - $scope.$watch('vm.valueType', function() { 51 + $scope.$watch('vm.valueType', function () {
44 if (vm.valueType === types.valueType.boolean) { 52 if (vm.valueType === types.valueType.boolean) {
45 vm.attribute.value = false; 53 vm.attribute.value = false;
  54 + } else if (vm.valueType === types.valueType.json) {
  55 + vm.attribute.value = {};
46 } else { 56 } else {
47 vm.attribute.value = null; 57 vm.attribute.value = null;
48 } 58 }
49 }); 59 });
  60 +
  61 + vm.addJSON = ($event) => {
  62 + showJsonDialog($event, vm.attribute.value, false).then((response) => {
  63 + vm.attribute.value = response;
  64 + })
  65 + };
  66 +
  67 + function showJsonDialog($event, jsonValue, readOnly) {
  68 + if ($event) {
  69 + $event.stopPropagation();
  70 + }
  71 + return $mdDialog.show({
  72 + controller: AttributeDialogEditJsonController,
  73 + controllerAs: 'vm',
  74 + templateUrl: attributeDialogEditJsonTemplate,
  75 + locals: {
  76 + jsonValue: jsonValue,
  77 + readOnly: readOnly
  78 + },
  79 + targetEvent: $event,
  80 + fullscreen: true,
  81 + multiple: true,
  82 + });
  83 + }
50 } 84 }
@@ -26,7 +26,8 @@ @@ -26,7 +26,8 @@
26 </md-button> 26 </md-button>
27 </div> 27 </div>
28 </md-toolbar> 28 </md-toolbar>
29 - <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> 29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading"
  30 + ng-show="$root.loading"></md-progress-linear>
30 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> 31 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
31 <md-dialog-content> 32 <md-dialog-content>
32 <div class="md-dialog-content"> 33 <div class="md-dialog-content">
@@ -58,7 +59,8 @@ @@ -58,7 +59,8 @@
58 </md-input-container> 59 </md-input-container>
59 <md-input-container ng-if="vm.valueType===vm.valueTypes.integer" flex="60" class="md-block"> 60 <md-input-container ng-if="vm.valueType===vm.valueTypes.integer" flex="60" class="md-block">
60 <label translate>value.integer-value</label> 61 <label translate>value.integer-value</label>
61 - <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attribute.value"> 62 + <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/"
  63 + ng-model="vm.attribute.value">
62 <div ng-messages="theForm.value.$error"> 64 <div ng-messages="theForm.value.$error">
63 <div translate ng-message="required">attribute.value-required</div> 65 <div translate ng-message="required">attribute.value-required</div>
64 <div translate ng-message="pattern">value.invalid-integer-value</div> 66 <div translate ng-message="pattern">value.invalid-integer-value</div>
@@ -71,11 +73,31 @@ @@ -71,11 +73,31 @@
71 <div translate ng-message="required">attribute.value-required</div> 73 <div translate ng-message="required">attribute.value-required</div>
72 </div> 74 </div>
73 </md-input-container> 75 </md-input-container>
74 - <div layout="column" layout-align="center" flex="60" ng-if="vm.valueType===vm.valueTypes.boolean">  
75 - <md-checkbox ng-model="vm.attribute.value" style="margin-bottom: 0px;"> 76 + <div layout="column" layout-align="center" flex="60"
  77 + ng-if="vm.valueType===vm.valueTypes.boolean">
  78 + <md-checkbox ng-model="vm.attribute.value" style="margin-bottom: 0;">
76 {{ (vm.attribute.value ? 'value.true' : 'value.false') | translate }} 79 {{ (vm.attribute.value ? 'value.true' : 'value.false') | translate }}
77 </md-checkbox> 80 </md-checkbox>
78 </div> 81 </div>
  82 + <div layout="row" layout-align="center" flex="60"
  83 + ng-if="vm.valueType===vm.valueTypes.json" class="md-block">
  84 + <md-input-container flex class="md-block">
  85 + <label translate>value.json-value</label>
  86 + <input required tb-json-to-string name="value" ng-model="vm.attribute.value">
  87 + <div ng-messages="theForm.value.$error">
  88 + <div translate ng-message="required">attribute.value-required</div>
  89 + </div>
  90 + </md-input-container>
  91 + <md-button class="md-icon-button"
  92 + ng-click="vm.addJSON($event)"
  93 + style="margin: 18px 0;"
  94 + aria-label="{{ 'action.edit' | translate }}">
  95 + <md-tooltip md-direction="top">
  96 + {{ 'action.edit' | translate }}
  97 + </md-tooltip>
  98 + <ng-md-icon size="20" icon="open_in_new"></ng-md-icon>
  99 + </md-button>
  100 + </div>
79 </section> 101 </section>
80 </fieldset> 102 </fieldset>
81 </md-content> 103 </md-content>
@@ -87,8 +109,8 @@ @@ -87,8 +109,8 @@
87 class="md-raised md-primary"> 109 class="md-raised md-primary">
88 {{ 'action.add' | translate }} 110 {{ 'action.add' | translate }}
89 </md-button> 111 </md-button>
90 - <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |  
91 - translate }} 112 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">
  113 + {{ 'action.cancel' | translate }}
92 </md-button> 114 </md-button>
93 </md-dialog-actions> 115 </md-dialog-actions>
94 </form> 116 </form>
@@ -151,5 +151,4 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, @@ -151,5 +151,4 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
151 } 151 }
152 ); 152 );
153 } 153 }
154 -  
155 } 154 }
@@ -26,7 +26,8 @@ @@ -26,7 +26,8 @@
26 </md-button> 26 </md-button>
27 </div> 27 </div>
28 </md-toolbar> 28 </md-toolbar>
29 - <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> 29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!$root.loading"
  30 + ng-show="$root.loading"></md-progress-linear>
30 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span> 31 <span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
31 <md-dialog-content> 32 <md-dialog-content>
32 <div class="md-dialog-content"> 33 <div class="md-dialog-content">
@@ -37,10 +38,10 @@ @@ -37,10 +38,10 @@
37 <section flex layout="column" style="width: 300px;"> 38 <section flex layout="column" style="width: 300px;">
38 <span translate style="padding-bottom: 10px;">dashboard.select-existing</span> 39 <span translate style="padding-bottom: 10px;">dashboard.select-existing</span>
39 <tb-dashboard-autocomplete the-form="theForm" 40 <tb-dashboard-autocomplete the-form="theForm"
40 - ng-disabled="$root.loading || vm.addToDashboardType != 0"  
41 - tb-required="vm.addToDashboardType === 0"  
42 - ng-model="vm.dashboardId"  
43 - select-first-dashboard="false"> 41 + ng-disabled="$root.loading || vm.addToDashboardType != 0"
  42 + tb-required="vm.addToDashboardType === 0"
  43 + ng-model="vm.dashboardId"
  44 + select-first-dashboard="false">
44 </tb-dashboard-autocomplete> 45 </tb-dashboard-autocomplete>
45 </section> 46 </section>
46 </md-radio-button> 47 </md-radio-button>
@@ -49,7 +50,8 @@ @@ -49,7 +50,8 @@
49 <span translate>dashboard.create-new</span> 50 <span translate>dashboard.create-new</span>
50 <md-input-container class="md-block"> 51 <md-input-container class="md-block">
51 <label translate>dashboard.new-dashboard-title</label> 52 <label translate>dashboard.new-dashboard-title</label>
52 - <input ng-required="vm.addToDashboardType === 1" name="title" ng-model="vm.newDashboard.title"> 53 + <input ng-required="vm.addToDashboardType === 1" name="title"
  54 + ng-model="vm.newDashboard.title">
53 <div ng-messages="theForm.title.$error"> 55 <div ng-messages="theForm.title.$error">
54 <div translate ng-message="required">dashboard.title-required</div> 56 <div translate ng-message="required">dashboard.title-required</div>
55 </div> 57 </div>
@@ -66,15 +68,15 @@ @@ -66,15 +68,15 @@
66 <md-checkbox 68 <md-checkbox
67 ng-model="vm.openDashboard" 69 ng-model="vm.openDashboard"
68 aria-label="{{ 'dashboard.open-dashboard' | translate }}" 70 aria-label="{{ 'dashboard.open-dashboard' | translate }}"
69 - style="margin-bottom: 0px; padding-right: 20px;"> 71 + style="margin-bottom: 0; padding-right: 20px;">
70 {{ 'dashboard.open-dashboard' | translate }} 72 {{ 'dashboard.open-dashboard' | translate }}
71 </md-checkbox> 73 </md-checkbox>
72 <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit" 74 <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit"
73 class="md-raised md-primary"> 75 class="md-raised md-primary">
74 {{ 'action.add' | translate }} 76 {{ 'action.add' | translate }}
75 </md-button> 77 </md-button>
76 - <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |  
77 - translate }} 78 + <md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">
  79 + {{ 'action.cancel' | translate }}
78 </md-button> 80 </md-button>
79 </md-dialog-actions> 81 </md-dialog-actions>
80 </form> 82 </form>
  1 +/*
  2 + * Copyright © 2016-2020 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 +/* eslint-enable import/no-unresolved, import/default */
  17 +
  18 +import './attribute-dialog-edit-json.scss';
  19 +
  20 +/*@ngInject*/
  21 +export default function AttributeDialogEditJsonController($mdDialog, types, jsonValue, readOnly) {
  22 +
  23 + let vm = this;
  24 + vm.json = angular.toJson(jsonValue, 4);
  25 + vm.readOnly = readOnly;
  26 + vm.contentType = types.contentType.JSON.value;
  27 +
  28 + vm.save = () => {
  29 + $mdDialog.hide(angular.fromJson(vm.json));
  30 + };
  31 +
  32 + vm.cancel = () => {
  33 + $mdDialog.cancel();
  34 + };
  35 +}
  1 +/**
  2 + * Copyright © 2016-2020 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 +.attribute-edit-json-dialog{
  17 + min-width: 400px;
  18 +}
  19 +
  20 +@media (max-width: 425px){
  21 + .attribute-edit-json-dialog {
  22 + min-width: 200px;
  23 + }
  24 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 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 +<md-dialog class="attribute-edit-json-dialog">
  19 + <form name="theForm" ng-submit="vm.save()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2>{{ 'details.edit-json' | translate }}</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button"
  25 + ng-click="vm.cancel()"
  26 + aria-label="{{'action.close'|translate}}">
  27 + <ng-md-icon icon="close" aria-label="Close"></ng-md-icon>
  28 + </md-button>
  29 + </div>
  30 + </md-toolbar>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <tb-json-content
  34 + ng-model="vm.json"
  35 + label="{{ 'value.json-value' | translate }}"
  36 + content-type="vm.contentType"
  37 + validate-content="true"
  38 + hide-error-toast="true"
  39 + fill-height="false"
  40 + ng-readonly="vm.readOnly">
  41 + </tb-json-content>
  42 + </div>
  43 + </md-dialog-content>
  44 + <md-dialog-actions layout="row" layout-align="end center" class="action-buttons">
  45 + <md-button ng-disabled="$root.loading || theForm.$invalid || !theForm.$dirty" type="submit"
  46 + class="md-raised md-primary">
  47 + {{'action.save'|translate}}
  48 + </md-button>
  49 + <md-button id="cancel-btn" ng-disabled="$root.loading" ng-click="vm.cancel()">
  50 + {{'action.cancel'|translate }}
  51 + </md-button>
  52 + </md-dialog-actions>
  53 + </form>
  54 +</md-dialog>
@@ -39,7 +39,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -39,7 +39,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
39 39
40 element.html(template); 40 element.html(template);
41 41
42 - var getAttributeScopeByValue = function(attributeScopeValue) { 42 + var getAttributeScopeByValue = function (attributeScopeValue) {
43 if (scope.types.latestTelemetry.value === attributeScopeValue) { 43 if (scope.types.latestTelemetry.value === attributeScopeValue) {
44 return scope.types.latestTelemetry; 44 return scope.types.latestTelemetry;
45 } 45 }
@@ -48,7 +48,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -48,7 +48,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
48 return scope.attributeScopes[attrScope]; 48 return scope.attributeScopes[attrScope];
49 } 49 }
50 } 50 }
51 - } 51 + };
52 52
53 scope.types = types; 53 scope.types = types;
54 54
@@ -87,14 +87,14 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -87,14 +87,14 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
87 search: null 87 search: null
88 }; 88 };
89 89
90 - scope.$watch("entityId", function(newVal) { 90 + scope.$watch("entityId", function (newVal) {
91 if (newVal) { 91 if (newVal) {
92 scope.resetFilter(); 92 scope.resetFilter();
93 scope.getEntityAttributes(false, true); 93 scope.getEntityAttributes(false, true);
94 } 94 }
95 }); 95 });
96 96
97 - scope.$watch("attributeScope", function(newVal, prevVal) { 97 + scope.$watch("attributeScope", function (newVal, prevVal) {
98 if (newVal && !angular.equals(newVal, prevVal)) { 98 if (newVal && !angular.equals(newVal, prevVal)) {
99 scope.mode = 'default'; 99 scope.mode = 'default';
100 scope.query.search = null; 100 scope.query.search = null;
@@ -103,30 +103,30 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -103,30 +103,30 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
103 } 103 }
104 }); 104 });
105 105
106 - scope.resetFilter = function() { 106 + scope.resetFilter = function () {
107 scope.mode = 'default'; 107 scope.mode = 'default';
108 scope.query.search = null; 108 scope.query.search = null;
109 scope.selectedAttributes = []; 109 scope.selectedAttributes = [];
110 scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope); 110 scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
111 - } 111 + };
112 112
113 - scope.enterFilterMode = function(event) { 113 + scope.enterFilterMode = function (event) {
114 let $button = angular.element(event.currentTarget); 114 let $button = angular.element(event.currentTarget);
115 let $toolbarsContainer = $button.closest('.toolbarsContainer'); 115 let $toolbarsContainer = $button.closest('.toolbarsContainer');
116 116
117 scope.query.search = ''; 117 scope.query.search = '';
118 118
119 - $timeout(()=>{ 119 + $timeout(() => {
120 $toolbarsContainer.find('.searchInput').focus(); 120 $toolbarsContainer.find('.searchInput').focus();
121 }) 121 })
122 - } 122 + };
123 123
124 - scope.exitFilterMode = function() { 124 + scope.exitFilterMode = function () {
125 scope.query.search = null; 125 scope.query.search = null;
126 scope.getEntityAttributes(); 126 scope.getEntityAttributes();
127 - } 127 + };
128 128
129 - scope.$watch("query.search", function(newVal, prevVal) { 129 + scope.$watch("query.search", function (newVal, prevVal) {
130 if (!angular.equals(newVal, prevVal) && scope.query.search != null) { 130 if (!angular.equals(newVal, prevVal) && scope.query.search != null) {
131 scope.getEntityAttributes(); 131 scope.getEntityAttributes();
132 } 132 }
@@ -142,15 +142,15 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -142,15 +142,15 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
142 } 142 }
143 } 143 }
144 144
145 - scope.onReorder = function() { 145 + scope.onReorder = function () {
146 scope.getEntityAttributes(false, false); 146 scope.getEntityAttributes(false, false);
147 - } 147 + };
148 148
149 - scope.onPaginate = function() { 149 + scope.onPaginate = function () {
150 scope.getEntityAttributes(false, false); 150 scope.getEntityAttributes(false, false);
151 - } 151 + };
152 152
153 - scope.getEntityAttributes = function(forceUpdate, reset) { 153 + scope.getEntityAttributes = function (forceUpdate, reset) {
154 if (scope.attributesDeferred) { 154 if (scope.attributesDeferred) {
155 scope.attributesDeferred.resolve(); 155 scope.attributesDeferred.resolve();
156 } 156 }
@@ -163,7 +163,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -163,7 +163,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
163 } 163 }
164 scope.checkSubscription(); 164 scope.checkSubscription();
165 scope.attributesDeferred = attributeService.getEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, 165 scope.attributesDeferred = attributeService.getEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value,
166 - scope.query, function(attributes, update, apply) { 166 + scope.query, function (attributes, update, apply) {
167 success(attributes, update || forceUpdate, apply); 167 success(attributes, update || forceUpdate, apply);
168 } 168 }
169 ); 169 );
@@ -176,9 +176,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -176,9 +176,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
176 }); 176 });
177 deferred.resolve(); 177 deferred.resolve();
178 } 178 }
179 - } 179 + };
180 180
181 - scope.checkSubscription = function() { 181 + scope.checkSubscription = function () {
182 var newSubscriptionId = null; 182 var newSubscriptionId = null;
183 if (scope.entityId && scope.entityType && scope.attributeScope.clientSide && scope.mode != 'widget') { 183 if (scope.entityId && scope.entityType && scope.attributeScope.clientSide && scope.mode != 'widget') {
184 newSubscriptionId = attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value); 184 newSubscriptionId = attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value);
@@ -187,36 +187,38 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -187,36 +187,38 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
187 attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); 187 attributeService.unsubscribeForEntityAttributes(scope.subscriptionId);
188 } 188 }
189 scope.subscriptionId = newSubscriptionId; 189 scope.subscriptionId = newSubscriptionId;
190 - } 190 + };
191 191
192 - scope.$on('$destroy', function() { 192 + scope.$on('$destroy', function () {
193 if (scope.subscriptionId) { 193 if (scope.subscriptionId) {
194 attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); 194 attributeService.unsubscribeForEntityAttributes(scope.subscriptionId);
195 } 195 }
196 }); 196 });
197 197
198 - scope.editAttribute = function($event, attribute) { 198 + scope.editAttribute = function ($event, attribute) {
199 if (!scope.attributeScope.clientSide) { 199 if (!scope.attributeScope.clientSide) {
200 $event.stopPropagation(); 200 $event.stopPropagation();
201 $mdEditDialog.show({ 201 $mdEditDialog.show({
202 controller: EditAttributeValueController, 202 controller: EditAttributeValueController,
203 templateUrl: editAttributeValueTemplate, 203 templateUrl: editAttributeValueTemplate,
204 - locals: {attributeValue: attribute.value,  
205 - save: function (model) {  
206 - var updatedAttribute = angular.copy(attribute);  
207 - updatedAttribute.value = model.value;  
208 - attributeService.saveEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, [updatedAttribute]).then(  
209 - function success() {  
210 - scope.getEntityAttributes();  
211 - }  
212 - );  
213 - }}, 204 + locals: {
  205 + attributeValue: attribute.value,
  206 + save: function (model) {
  207 + var updatedAttribute = angular.copy(attribute);
  208 + updatedAttribute.value = model.value;
  209 + attributeService.saveEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, [updatedAttribute]).then(
  210 + function success() {
  211 + scope.getEntityAttributes();
  212 + }
  213 + );
  214 + }
  215 + },
214 targetEvent: $event 216 targetEvent: $event
215 }); 217 });
216 } 218 }
217 - } 219 + };
218 220
219 - scope.addAttribute = function($event) { 221 + scope.addAttribute = function ($event) {
220 if (!scope.attributeScope.clientSide) { 222 if (!scope.attributeScope.clientSide) {
221 $event.stopPropagation(); 223 $event.stopPropagation();
222 $mdDialog.show({ 224 $mdDialog.show({
@@ -224,16 +226,20 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -224,16 +226,20 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
224 controllerAs: 'vm', 226 controllerAs: 'vm',
225 templateUrl: addAttributeDialogTemplate, 227 templateUrl: addAttributeDialogTemplate,
226 parent: angular.element($document[0].body), 228 parent: angular.element($document[0].body),
227 - locals: {entityType: scope.entityType, entityId: scope.entityId, attributeScope: scope.attributeScope.value}, 229 + locals: {
  230 + entityType: scope.entityType,
  231 + entityId: scope.entityId,
  232 + attributeScope: scope.attributeScope.value
  233 + },
228 fullscreen: true, 234 fullscreen: true,
229 targetEvent: $event 235 targetEvent: $event
230 }).then(function () { 236 }).then(function () {
231 scope.getEntityAttributes(); 237 scope.getEntityAttributes();
232 }); 238 });
233 } 239 }
234 - } 240 + };
235 241
236 - scope.deleteAttributes = function($event) { 242 + scope.deleteAttributes = function ($event) {
237 if (!scope.attributeScope.clientSide) { 243 if (!scope.attributeScope.clientSide) {
238 $event.stopPropagation(); 244 $event.stopPropagation();
239 var confirm = $mdDialog.confirm() 245 var confirm = $mdDialog.confirm()
@@ -244,33 +250,33 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -244,33 +250,33 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
244 .cancel($translate.instant('action.no')) 250 .cancel($translate.instant('action.no'))
245 .ok($translate.instant('action.yes')); 251 .ok($translate.instant('action.yes'));
246 $mdDialog.show(confirm).then(function () { 252 $mdDialog.show(confirm).then(function () {
247 - attributeService.deleteEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, scope.selectedAttributes).then(  
248 - function success() {  
249 - scope.selectedAttributes = [];  
250 - scope.getEntityAttributes();  
251 - }  
252 - ) 253 + attributeService.deleteEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, scope.selectedAttributes).then(
  254 + function success() {
  255 + scope.selectedAttributes = [];
  256 + scope.getEntityAttributes();
  257 + }
  258 + )
253 }); 259 });
254 } 260 }
255 - } 261 + };
256 262
257 - scope.nextWidget = function() { 263 + scope.nextWidget = function () {
258 $mdUtil.nextTick(function () { 264 $mdUtil.nextTick(function () {
259 if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) { 265 if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) {
260 scope.widgetsCarousel.index++; 266 scope.widgetsCarousel.index++;
261 } 267 }
262 }); 268 });
263 - } 269 + };
264 270
265 - scope.prevWidget = function() { 271 + scope.prevWidget = function () {
266 $mdUtil.nextTick(function () { 272 $mdUtil.nextTick(function () {
267 if (scope.widgetsCarousel.index > 0) { 273 if (scope.widgetsCarousel.index > 0) {
268 scope.widgetsCarousel.index--; 274 scope.widgetsCarousel.index--;
269 } 275 }
270 }); 276 });
271 - } 277 + };
272 278
273 - scope.enterWidgetMode = function() { 279 + scope.enterWidgetMode = function () {
274 280
275 if (scope.widgetsIndexWatch) { 281 if (scope.widgetsIndexWatch) {
276 scope.widgetsIndexWatch(); 282 scope.widgetsIndexWatch();
@@ -303,7 +309,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -303,7 +309,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
303 entitiAliases[entityAlias.id] = entityAlias; 309 entitiAliases[entityAlias.id] = entityAlias;
304 310
305 var stateController = { 311 var stateController = {
306 - getStateParams: function() { 312 + getStateParams: function () {
307 return {}; 313 return {};
308 } 314 }
309 }; 315 };
@@ -317,9 +323,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -317,9 +323,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
317 type: types.datasourceType.entity, 323 type: types.datasourceType.entity,
318 entityAliasId: entityAlias.id, 324 entityAliasId: entityAlias.id,
319 dataKeys: [] 325 dataKeys: []
320 - } 326 + };
321 var i = 0; 327 var i = 0;
322 - for (var attr =0; attr < scope.selectedAttributes.length;attr++) { 328 + for (var attr = 0; attr < scope.selectedAttributes.length; attr++) {
323 var attribute = scope.selectedAttributes[attr]; 329 var attribute = scope.selectedAttributes[attr];
324 var dataKey = { 330 var dataKey = {
325 name: attribute.key, 331 name: attribute.key,
@@ -328,12 +334,12 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -328,12 +334,12 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
328 color: utils.getMaterialColor(i), 334 color: utils.getMaterialColor(i),
329 settings: {}, 335 settings: {},
330 _hash: Math.random() 336 _hash: Math.random()
331 - } 337 + };
332 datasource.dataKeys.push(dataKey); 338 datasource.dataKeys.push(dataKey);
333 i++; 339 i++;
334 } 340 }
335 341
336 - scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function(newVal, prevVal) { 342 + scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function (newVal, prevVal) {
337 if (scope.mode === 'widget' && (newVal != prevVal)) { 343 if (scope.mode === 'widget' && (newVal != prevVal)) {
338 var index = scope.widgetsCarousel.index; 344 var index = scope.widgetsCarousel.index;
339 for (var i = 0; i < scope.widgetsList.length; i++) { 345 for (var i = 0; i < scope.widgetsList.length; i++) {
@@ -345,7 +351,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -345,7 +351,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
345 } 351 }
346 }); 352 });
347 353
348 - scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function(newVal, prevVal) { 354 + scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function (newVal, prevVal) {
349 if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) { 355 if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) {
350 scope.widgetsList = []; 356 scope.widgetsList = [];
351 scope.widgetsListCache = []; 357 scope.widgetsListCache = [];
@@ -358,7 +364,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -358,7 +364,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
358 widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then( 364 widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then(
359 function success(widgetTypes) { 365 function success(widgetTypes) {
360 366
361 - widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']); 367 + widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type', '-createdTime']);
362 368
363 for (var i = 0; i < widgetTypes.length; i++) { 369 for (var i = 0; i < widgetTypes.length; i++) {
364 var widgetType = widgetTypes[i]; 370 var widgetType = widgetTypes[i];
@@ -398,9 +404,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -398,9 +404,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
398 } 404 }
399 } 405 }
400 }); 406 });
401 - } 407 + };
402 408
403 - scope.exitWidgetMode = function() { 409 + scope.exitWidgetMode = function () {
404 if (scope.widgetsBundleWatch) { 410 if (scope.widgetsBundleWatch) {
405 scope.widgetsBundleWatch(); 411 scope.widgetsBundleWatch();
406 scope.widgetsBundleWatch = null; 412 scope.widgetsBundleWatch = null;
@@ -412,9 +418,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -412,9 +418,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
412 scope.selectedWidgetsBundleAlias = null; 418 scope.selectedWidgetsBundleAlias = null;
413 scope.mode = 'default'; 419 scope.mode = 'default';
414 scope.getEntityAttributes(true); 420 scope.getEntityAttributes(true);
415 - } 421 + };
416 422
417 - scope.addWidgetToDashboard = function($event) { 423 + scope.addWidgetToDashboard = function ($event) {
418 if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) { 424 if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) {
419 var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0]; 425 var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0];
420 $event.stopPropagation(); 426 $event.stopPropagation();
@@ -423,21 +429,26 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -423,21 +429,26 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
423 controllerAs: 'vm', 429 controllerAs: 'vm',
424 templateUrl: addWidgetToDashboardDialogTemplate, 430 templateUrl: addWidgetToDashboardDialogTemplate,
425 parent: angular.element($document[0].body), 431 parent: angular.element($document[0].body),
426 - locals: {entityId: scope.entityId, entityType: scope.entityType, entityName: scope.entityName, widget: angular.copy(widget)}, 432 + locals: {
  433 + entityId: scope.entityId,
  434 + entityType: scope.entityType,
  435 + entityName: scope.entityName,
  436 + widget: angular.copy(widget)
  437 + },
427 fullscreen: true, 438 fullscreen: true,
428 targetEvent: $event 439 targetEvent: $event
429 }).then(function () { 440 }).then(function () {
430 441
431 }); 442 });
432 } 443 }
433 - } 444 + };
434 445
435 - scope.loading = function() { 446 + scope.loading = function () {
436 return $rootScope.loading; 447 return $rootScope.loading;
437 - } 448 + };
438 449
439 $compile(element.contents())(scope); 450 $compile(element.contents())(scope);
440 - } 451 + };
441 452
442 return { 453 return {
443 restrict: "E", 454 restrict: "E",
@@ -58,3 +58,11 @@ md-toolbar.md-table-toolbar.alternate { @@ -58,3 +58,11 @@ md-toolbar.md-table-toolbar.alternate {
58 } 58 }
59 } 59 }
60 } 60 }
  61 +
  62 +md-edit-dialog.tb-edit-dialog{
  63 + z-index: 78;
  64 +}
  65 +
  66 +md-backdrop.md-edit-dialog-backdrop{
  67 + z-index: 77;
  68 +}
@@ -77,9 +77,7 @@ @@ -77,9 +77,7 @@
77 </md-toolbar> 77 </md-toolbar>
78 <md-toolbar class="md-table-toolbar alternate" ng-show="mode==='default' && selectedAttributes.length"> 78 <md-toolbar class="md-table-toolbar alternate" ng-show="mode==='default' && selectedAttributes.length">
79 <div class="md-toolbar-tools"> 79 <div class="md-toolbar-tools">
80 - <span translate="{{attributeScope === types.latestTelemetry  
81 - ? 'attribute.selected-telemetry'  
82 - : 'attribute.selected-attributes'}}" 80 + <span translate="{{(attributeScope === types.latestTelemetry) ? 'attribute.selected-telemetry' : 'attribute.selected-attributes'}}"
83 translate-values="{count: selectedAttributes.length}" 81 translate-values="{count: selectedAttributes.length}"
84 translate-interpolation="messageformat"></span> 82 translate-interpolation="messageformat"></span>
85 <span flex></span> 83 <span flex></span>
@@ -13,14 +13,19 @@ @@ -13,14 +13,19 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
  16 +/* eslint-enable import/no-unresolved, import/default */
  17 +
  18 +import AttributeDialogEditJsonController from "./attribute-dialog-edit-json.controller";
  19 +import attributeDialogEditJsonTemplate from "./attribute-dialog-edit-json.tpl.html";
  20 +
16 /*@ngInject*/ 21 /*@ngInject*/
17 -export default function EditAttributeValueController($scope, $q, $element, types, attributeValue, save) { 22 +export default function EditAttributeValueController($scope, $mdDialog, $q, $element, $document, types, attributeValue, save) {
18 23
19 $scope.valueTypes = types.valueType; 24 $scope.valueTypes = types.valueType;
20 25
21 - $scope.model = {};  
22 -  
23 - $scope.model.value = attributeValue; 26 + $scope.model = {
  27 + value: attributeValue
  28 + };
24 29
25 if ($scope.model.value === true || $scope.model.value === false) { 30 if ($scope.model.value === true || $scope.model.value === false) {
26 $scope.valueType = types.valueType.boolean; 31 $scope.valueType = types.valueType.boolean;
@@ -30,26 +35,27 @@ export default function EditAttributeValueController($scope, $q, $element, types @@ -30,26 +35,27 @@ export default function EditAttributeValueController($scope, $q, $element, types
30 } else { 35 } else {
31 $scope.valueType = types.valueType.double; 36 $scope.valueType = types.valueType.double;
32 } 37 }
  38 + } else if (angular.isObject($scope.model.value)) {
  39 + $scope.valueType = types.valueType.json;
33 } else { 40 } else {
34 $scope.valueType = types.valueType.string; 41 $scope.valueType = types.valueType.string;
35 } 42 }
36 43
37 $scope.submit = submit; 44 $scope.submit = submit;
38 $scope.dismiss = dismiss; 45 $scope.dismiss = dismiss;
  46 + $scope.editJSON = editJSON;
39 47
40 function dismiss() { 48 function dismiss() {
41 $element.remove(); 49 $element.remove();
42 } 50 }
43 51
44 function update() { 52 function update() {
45 - if($scope.editDialog.$invalid) { 53 + if ($scope.editDialog.$invalid) {
46 return $q.reject(); 54 return $q.reject();
47 } 55 }
48 -  
49 - if(angular.isFunction(save)) { 56 + if (angular.isFunction(save)) {
50 return $q.when(save($scope.model)); 57 return $q.when(save($scope.model));
51 } 58 }
52 -  
53 return $q.resolve(); 59 return $q.resolve();
54 } 60 }
55 61
@@ -59,13 +65,45 @@ export default function EditAttributeValueController($scope, $q, $element, types @@ -59,13 +65,45 @@ export default function EditAttributeValueController($scope, $q, $element, types
59 }); 65 });
60 } 66 }
61 67
62 - $scope.$watch('valueType', function(newVal, prevVal) {  
63 - if (newVal != prevVal) { 68 +
  69 + $scope.$watch('valueType', function (newVal, prevVal) {
  70 + if (newVal !== prevVal) {
64 if ($scope.valueType === types.valueType.boolean) { 71 if ($scope.valueType === types.valueType.boolean) {
65 $scope.model.value = false; 72 $scope.model.value = false;
  73 + } else if ($scope.valueType === types.valueType.json) {
  74 + $scope.model.value = {};
66 } else { 75 } else {
67 $scope.model.value = null; 76 $scope.model.value = null;
68 } 77 }
69 } 78 }
70 }); 79 });
  80 +
  81 + function editJSON($event) {
  82 + $scope.hideDialog = true;
  83 + showJsonDialog($event, $scope.model.value, false).then((response) => {
  84 + $scope.hideDialog = false;
  85 + if (!angular.equals(response, $scope.model.value)) {
  86 + $scope.editDialog.$setDirty();
  87 + }
  88 + $scope.model.value = response;
  89 + })
  90 + }
  91 +
  92 + function showJsonDialog($event, jsonValue, readOnly) {
  93 + if ($event) {
  94 + $event.stopPropagation();
  95 + }
  96 + return $mdDialog.show({
  97 + controller: AttributeDialogEditJsonController,
  98 + controllerAs: 'vm',
  99 + templateUrl: attributeDialogEditJsonTemplate,
  100 + locals: {
  101 + jsonValue: jsonValue,
  102 + readOnly: readOnly
  103 + },
  104 + targetEvent: $event,
  105 + fullscreen: true,
  106 + multiple: true
  107 + });
  108 + }
71 } 109 }
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<md-edit-dialog>  
19 - <form name="editDialog" ng-submit="submit()"> 18 +<md-edit-dialog class="tb-edit-dialog">
  19 + <form name="editDialog" ng-submit="submit()" >
20 <div layout="column" class="md-content" style="width: 400px;"> 20 <div layout="column" class="md-content" style="width: 400px;">
21 <fieldset> 21 <fieldset>
22 <section layout="row"> 22 <section layout="row">
@@ -38,7 +38,8 @@ @@ -38,7 +38,8 @@
38 </md-input-container> 38 </md-input-container>
39 <md-input-container ng-if="valueType===valueTypes.integer" flex="60" class="md-block"> 39 <md-input-container ng-if="valueType===valueTypes.integer" flex="60" class="md-block">
40 <label translate>value.integer-value</label> 40 <label translate>value.integer-value</label>
41 - <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="model.value"> 41 + <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/"
  42 + ng-model="model.value">
42 <div ng-messages="editDialog.value.$error"> 43 <div ng-messages="editDialog.value.$error">
43 <div translate ng-message="required">attribute.value-required</div> 44 <div translate ng-message="required">attribute.value-required</div>
44 <div translate ng-message="pattern">value.invalid-integer-value</div> 45 <div translate ng-message="pattern">value.invalid-integer-value</div>
@@ -52,16 +53,31 @@ @@ -52,16 +53,31 @@
52 </div> 53 </div>
53 </md-input-container> 54 </md-input-container>
54 <div layout="column" layout-align="center" flex="60" ng-if="valueType===valueTypes.boolean"> 55 <div layout="column" layout-align="center" flex="60" ng-if="valueType===valueTypes.boolean">
55 - <md-checkbox ng-model="model.value" style="margin-bottom: 0px;"> 56 + <md-checkbox ng-model="model.value" style="margin-bottom: 0;">
56 {{ (model.value ? 'value.true' : 'value.false') | translate }} 57 {{ (model.value ? 'value.true' : 'value.false') | translate }}
57 </md-checkbox> 58 </md-checkbox>
58 </div> 59 </div>
  60 + <div layout="row" layout-align="center" ng-if="valueType===valueTypes.json" flex="60">
  61 + <md-input-container class="md-block">
  62 + <label translate>value.json-value</label>
  63 + <input required tb-json-to-string name="value" ng-model="model.value">
  64 + <div ng-messages="editDialog.value.$error">
  65 + <div translate ng-message="required">attribute.value-required</div>
  66 + </div>
  67 + </md-input-container>
  68 + <md-button class="md-icon-button" style="margin: 18px 0;"
  69 + ng-click="editJSON($event)" aria-label="{{ 'action.edit' | translate }}">
  70 + <md-tooltip md-direction="top">
  71 + {{ 'action.edit' | translate }}
  72 + </md-tooltip>
  73 + <ng-md-icon size="20" icon="open_in_new"></ng-md-icon>
  74 + </md-button>
  75 + </div>
59 </section> 76 </section>
60 </fieldset> 77 </fieldset>
61 </div> 78 </div>
62 <div layout="row" layout-align="end" class="md-actions"> 79 <div layout="row" layout-align="end" class="md-actions">
63 - <md-button ng-click="dismiss()">{{ 'action.cancel' |  
64 - translate }} 80 + <md-button ng-click="dismiss()">{{ 'action.cancel' | translate }}
65 </md-button> 81 </md-button>
66 <md-button ng-disabled="editDialog.$invalid || !editDialog.$dirty" type="submit" 82 <md-button ng-disabled="editDialog.$invalid || !editDialog.$dirty" type="submit"
67 class="md-raised md-primary"> 83 class="md-raised md-primary">
@@ -69,4 +85,4 @@ @@ -69,4 +85,4 @@
69 </md-button> 85 </md-button>
70 </div> 86 </div>
71 </form> 87 </form>
72 -</md-edit-dialog>  
  88 +</md-edit-dialog>
@@ -2232,7 +2232,7 @@ @@ -2232,7 +2232,7 @@
2232 "last": "Τελευταίος", 2232 "last": "Τελευταίος",
2233 "time-period": "Χρονική Περίοδος" 2233 "time-period": "Χρονική Περίοδος"
2234 }, 2234 },
2235 - "user": { 2235 + "user": {
2236 "user": "Χρήστης", 2236 "user": "Χρήστης",
2237 "users": "Χρήστες", 2237 "users": "Χρήστες",
2238 "management": "Διαχείριση Χρηστών", 2238 "management": "Διαχείριση Χρηστών",
@@ -607,6 +607,7 @@ @@ -607,6 +607,7 @@
607 }, 607 },
608 "details": { 608 "details": {
609 "edit-mode": "Edit mode", 609 "edit-mode": "Edit mode",
  610 + "edit-json": "Edit JSON",
610 "toggle-edit-mode": "Toggle edit mode" 611 "toggle-edit-mode": "Toggle edit mode"
611 }, 612 },
612 "device": { 613 "device": {
@@ -1270,7 +1271,8 @@ @@ -1270,7 +1271,8 @@
1270 "js-func": { 1271 "js-func": {
1271 "no-return-error": "Function must return value!", 1272 "no-return-error": "Function must return value!",
1272 "return-type-mismatch": "Function must return value of '{{type}}' type!", 1273 "return-type-mismatch": "Function must return value of '{{type}}' type!",
1273 - "tidy": "Tidy" 1274 + "tidy": "Tidy",
  1275 + "mini": "Mini"
1274 }, 1276 },
1275 "key-val": { 1277 "key-val": {
1276 "key": "Key", 1278 "key": "Key",
@@ -1593,7 +1595,9 @@ @@ -1593,7 +1595,9 @@
1593 "boolean-value": "Boolean value", 1595 "boolean-value": "Boolean value",
1594 "false": "False", 1596 "false": "False",
1595 "true": "True", 1597 "true": "True",
1596 - "long": "Long" 1598 + "long": "Long",
  1599 + "json": "JSON",
  1600 + "json-value": "JSON value"
1597 }, 1601 },
1598 "widget": { 1602 "widget": {
1599 "widget-library": "Widgets Library", 1603 "widget-library": "Widgets Library",
@@ -607,6 +607,7 @@ @@ -607,6 +607,7 @@
607 }, 607 },
608 "details": { 608 "details": {
609 "edit-mode": "Режим редактирования", 609 "edit-mode": "Режим редактирования",
  610 + "edit-json": "Редактировать JSON",
610 "toggle-edit-mode": "Режим редактирования" 611 "toggle-edit-mode": "Режим редактирования"
611 }, 612 },
612 "device": { 613 "device": {
@@ -1191,8 +1192,7 @@ @@ -1191,8 +1192,7 @@
1191 }, 1192 },
1192 "js-func": { 1193 "js-func": {
1193 "no-return-error": "Функция должна возвращать значение!", 1194 "no-return-error": "Функция должна возвращать значение!",
1194 - "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!",  
1195 - "tidy": "Tidy" 1195 + "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!"
1196 }, 1196 },
1197 "key-val": { 1197 "key-val": {
1198 "key": "Ключ", 1198 "key": "Ключ",
@@ -724,6 +724,7 @@ @@ -724,6 +724,7 @@
724 "details": { 724 "details": {
725 "details": "Деталі", 725 "details": "Деталі",
726 "edit-mode": "Режим редагування", 726 "edit-mode": "Режим редагування",
  727 + "edit-json": "Редагувати JSON",
727 "toggle-edit-mode": "Перемкнути режим редагування" 728 "toggle-edit-mode": "Перемкнути режим редагування"
728 }, 729 },
729 "device": { 730 "device": {
@@ -1606,8 +1607,7 @@ @@ -1606,8 +1607,7 @@
1606 }, 1607 },
1607 "js-func": { 1608 "js-func": {
1608 "no-return-error": "Функція повинна повертати значення!", 1609 "no-return-error": "Функція повинна повертати значення!",
1609 - "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!",  
1610 - "tidy": "Tidy" 1610 + "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!"
1611 }, 1611 },
1612 "key-val": { 1612 "key-val": {
1613 "key": "Ключ", 1613 "key": "Ключ",