Commit 6091f1a8d8806916b7635035a243c7c799ae52be

Authored by Mirco Pizzichini
1 parent a568564e

Add new widget 'multiple-widget' to bundle 'input_widgets'

@@ -196,6 +196,22 @@ @@ -196,6 +196,22 @@
196 "dataKeySettingsSchema": "{}\n", 196 "dataKeySettingsSchema": "{}\n",
197 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" 197 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
198 } 198 }
  199 + },
  200 + {
  201 + "alias": "update_multiple_attributes",
  202 + "name": "Update Multiple Attributes",
  203 + "descriptor": {
  204 + "type": "latest",
  205 + "sizeX": 7.5,
  206 + "sizeY": 3.5,
  207 + "resources": [],
  208 + "templateHtml": "<tb-multiple-input-widget \n form-id=\"formId\"\n ctx=\"ctx\">\n</tb-multiple-input-widget>",
  209 + "templateCss": "",
  210 + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n",
  211 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Multiple input title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"attributesShared\": {\n \"title\": \"Attributes are 'shared' (default value is 'server')\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"attributesShared\",\n \"showResultMessage\"\n ]\n}",
  212 + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"inputTypeNumber\": {\n \"title\": \"Datakey is a number\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between valid values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"inputTypeNumber\",\n \"step\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t},\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n",
  213 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
  214 + }
199 } 215 }
200 ] 216 ]
201 -}  
  217 +}
@@ -24,6 +24,7 @@ import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget' @@ -24,6 +24,7 @@ import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget'
24 import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy-widget'; 24 import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy-widget';
25 import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget'; 25 import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget';
26 import thingsboardDateRangeNavigatorWidget from '../widget/lib/date-range-navigator/date-range-navigator'; 26 import thingsboardDateRangeNavigatorWidget from '../widget/lib/date-range-navigator/date-range-navigator';
  27 +import thingsboardMultipleInputWidget from '../widget/lib/multiple-input-widget';
27 28
28 import thingsboardRpcWidgets from '../widget/lib/rpc'; 29 import thingsboardRpcWidgets from '../widget/lib/rpc';
29 30
@@ -49,7 +50,7 @@ import thingsboardUtils from '../common/utils.service'; @@ -49,7 +50,7 @@ import thingsboardUtils from '../common/utils.service';
49 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, 50 export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight,
50 thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, 51 thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget,
51 thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, 52 thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget,
52 - thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils, TripAnimationWidget]) 53 + thingsboardMultipleInputWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils, TripAnimationWidget])
53 .factory('widgetService', WidgetService) 54 .factory('widgetService', WidgetService)
54 .name; 55 .name;
55 56
@@ -49,7 +49,8 @@ @@ -49,7 +49,8 @@
49 "import": "Import", 49 "import": "Import",
50 "export": "Export", 50 "export": "Export",
51 "share-via": "Share via {{provider}}", 51 "share-via": "Share via {{provider}}",
52 - "continue": "Continue" 52 + "continue": "Continue",
  53 + "discard-changes": "Discard Changes"
53 }, 54 },
54 "aggregation": { 55 "aggregation": {
55 "aggregation": "Aggregation", 56 "aggregation": "Aggregation",
@@ -1694,4 +1695,4 @@ @@ -1694,4 +1695,4 @@
1694 "cs_CZ": "Czech" 1695 "cs_CZ": "Czech"
1695 } 1696 }
1696 } 1697 }
1697 -}  
  1698 +}
@@ -48,7 +48,8 @@ @@ -48,7 +48,8 @@
48 "paste-reference": "Pegar referencia", 48 "paste-reference": "Pegar referencia",
49 "import": "Importar", 49 "import": "Importar",
50 "export": "Exportar", 50 "export": "Exportar",
51 - "share-via": "Compartir via {{provider}}" 51 + "share-via": "Compartir via {{provider}}",
  52 + "discard-changes": "Cancelar los cambios"
52 }, 53 },
53 "aggregation": { 54 "aggregation": {
54 "aggregation": "Agregación", 55 "aggregation": "Agregación",
@@ -48,7 +48,8 @@ @@ -48,7 +48,8 @@
48 "undo": "Annuler", 48 "undo": "Annuler",
49 "update": "mise à jour", 49 "update": "mise à jour",
50 "view": "Afficher", 50 "view": "Afficher",
51 - "yes": "Oui" 51 + "yes": "Oui",
  52 + "discard-changes": "Annuler les modifications"
52 }, 53 },
53 "admin": { 54 "admin": {
54 "base-url": "URL de base", 55 "base-url": "URL de base",
@@ -48,7 +48,8 @@ @@ -48,7 +48,8 @@
48 "paste-reference": "Incolla riferimento", 48 "paste-reference": "Incolla riferimento",
49 "import": "Importa", 49 "import": "Importa",
50 "export": "Esporta", 50 "export": "Esporta",
51 - "share-via": "Condividi con {{provider}}" 51 + "share-via": "Condividi con {{provider}}",
  52 + "discard-changes": "Annulla le modifiche"
52 }, 53 },
53 "aggregation": { 54 "aggregation": {
54 "aggregation": "Aggregazione", 55 "aggregation": "Aggregazione",
  1 +/*
  2 + * Copyright © 2016-2019 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 +import './multiple-input-widget.scss';
  17 +
  18 +/* eslint-disable import/no-unresolved, import/default */
  19 +
  20 +import multipleInputWidgetTemplate from './multiple-input-widget.tpl.html';
  21 +
  22 +/* eslint-enable import/no-unresolved, import/default */
  23 +
  24 +export default angular.module('thingsboard.widgets.multipleInputWidget', [])
  25 + .directive('tbMultipleInputWidget', MultipleInputWidget)
  26 + .name;
  27 +
  28 +/*@ngInject*/
  29 +function MultipleInputWidget() {
  30 + return {
  31 + restrict: "E",
  32 + scope: true,
  33 + bindToController: {
  34 + formId: '=',
  35 + ctx: '='
  36 + },
  37 + controller: MultipleInputWidgetController,
  38 + controllerAs: 'vm',
  39 + templateUrl: multipleInputWidgetTemplate
  40 + };
  41 +}
  42 +
  43 +/*@ngInject*/
  44 +function MultipleInputWidgetController($q, $scope, attributeService, toast, types, utils) {
  45 + var vm = this;
  46 +
  47 + vm.dataKeyDetected = false;
  48 + vm.hasAnyChange = false;
  49 + vm.entityDetected = false;
  50 + vm.isValidParameter = true;
  51 + vm.message = 'No entity selected';
  52 +
  53 + vm.rows = [];
  54 + vm.rowIndex = 0;
  55 +
  56 + vm.datasources = null;
  57 +
  58 + vm.cellStyle = cellStyle;
  59 + vm.discardAll = discardAll;
  60 + vm.inputChanged = inputChanged;
  61 + vm.postData = postData;
  62 +
  63 + $scope.$watch('vm.ctx', function() {
  64 + if (vm.ctx && vm.ctx.defaultSubscription) {
  65 + vm.settings = vm.ctx.settings;
  66 + vm.widgetConfig = vm.ctx.widgetConfig;
  67 + vm.subscription = vm.ctx.defaultSubscription;
  68 + vm.datasources = vm.subscription.datasources;
  69 + initializeConfig();
  70 + updateDatasources();
  71 + }
  72 + });
  73 +
  74 + $scope.$on('multiple-input-data-updated', function(event, formId) {
  75 + if (vm.formId == formId) {
  76 + updateRowData(vm.subscription.data);
  77 + $scope.$digest();
  78 + }
  79 + });
  80 +
  81 + function defaultStyle() {
  82 + return {};
  83 + }
  84 +
  85 + function cellStyle(key) {
  86 + var style = {};
  87 + if (key) {
  88 + var styleInfo = vm.stylesInfo[key.label];
  89 + var value = key.currentValue;
  90 + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {
  91 + try {
  92 + style = styleInfo.cellStyleFunction(value);
  93 + } catch (e) {
  94 + style = {};
  95 + }
  96 + } else {
  97 + style = defaultStyle();
  98 + }
  99 + }
  100 + return style;
  101 + }
  102 +
  103 + function discardAll() {
  104 + for (var r = 0; r < vm.rows.length; r++) {
  105 + var row = vm.rows[r];
  106 + for (var d = 0; d < row.data.length; d++ ) {
  107 + row.data[d].currentValue = row.data[d].originalValue;
  108 + }
  109 + }
  110 + vm.hasAnyChange = false;
  111 + }
  112 +
  113 + function inputChanged() {
  114 + var newValue = false;
  115 + for (var r = 0; r < vm.rows.length; r++) {
  116 + var row = vm.rows[r];
  117 + for (var d = 0; d < row.data.length; d++ ) {
  118 + if (!row.data[d].currentValue) {
  119 + return;
  120 + }
  121 + if (row.data[d].currentValue !== row.data[d].originalValue) {
  122 + newValue = true;
  123 + }
  124 + }
  125 + }
  126 + vm.hasAnyChange = newValue;
  127 + }
  128 +
  129 + function postData() {
  130 + var promises = [];
  131 + for (var r = 0; r < vm.rows.length; r++) {
  132 + var row = vm.rows[r];
  133 + var datasource = row.datasource;
  134 + var attributes = [];
  135 + var newValues = false;
  136 +
  137 + for (var d = 0; d < row.data.length; d++ ) {
  138 + if (row.data[d].currentValue !== row.data[d].originalValue) {
  139 + attributes.push({
  140 + key : row.data[d].name,
  141 + value : row.data[d].currentValue,
  142 + });
  143 + newValues = true;
  144 + }
  145 + }
  146 +
  147 + if (newValues) {
  148 + promises.push(attributeService.saveEntityAttributes(
  149 + datasource.entityType,
  150 + datasource.entityId,
  151 + vm.attributeScope,
  152 + attributes));
  153 + }
  154 + }
  155 +
  156 + if (promises.length) {
  157 + $q.all(promises).then(
  158 + function success() {
  159 + for (var d = 0; d < row.data.length; d++ ) {
  160 + row.data[d].originalValue = row.data[d].currentValue;
  161 + }
  162 + vm.hasAnyChange = false;
  163 + if (vm.settings.showResultMessage) {
  164 + toast.showSuccess('Update successful', 1000, angular.element(vm.ctx.$container), 'bottom left');
  165 + }
  166 + },
  167 + function fail() {
  168 + if (vm.settings.showResultMessage) {
  169 + toast.showError('Update failed', angular.element(vm.ctx.$container), 'bottom left');
  170 + }
  171 + }
  172 + );
  173 + }
  174 + }
  175 +
  176 + function initializeConfig() {
  177 +
  178 + if (vm.settings.widgetTitle && vm.settings.widgetTitle.length) {
  179 + vm.widgetTitle = utils.customTranslation(vm.settings.widgetTitle, vm.settings.widgetTitle);
  180 + } else {
  181 + vm.widgetTitle = vm.ctx.widgetConfig.title;
  182 + }
  183 +
  184 + vm.ctx.widgetTitle = vm.widgetTitle;
  185 +
  186 + vm.attributeScope = vm.settings.attributesShared ? types.attributesScope.shared.value : types.attributesScope.server.value;
  187 + }
  188 +
  189 + function updateDatasources() {
  190 +
  191 + vm.stylesInfo = {};
  192 + vm.rows = [];
  193 + vm.rowIndex = 0;
  194 +
  195 + if (vm.datasources) {
  196 + vm.entityDetected = true;
  197 + for (var ds = 0; ds < vm.datasources.length; ds++) {
  198 + var row = {};
  199 + var datasource = vm.datasources[ds];
  200 + row.datasource = datasource;
  201 + row.data = [];
  202 + if (datasource.dataKeys) {
  203 + vm.dataKeyDetected = true;
  204 + for (var a = 0; a < datasource.dataKeys.length; a++ ) {
  205 + var dataKey = datasource.dataKeys[a];
  206 +
  207 + if (dataKey.units) {
  208 + dataKey.label += ' (' + dataKey.units + ')';
  209 + }
  210 +
  211 + var keySettings = dataKey.settings;
  212 + if (keySettings.inputTypeNumber) {
  213 + keySettings.inputType = 'number';
  214 + } else {
  215 + keySettings.inputType = 'text';
  216 + }
  217 +
  218 + var cellStyleFunction = null;
  219 + var useCellStyleFunction = false;
  220 +
  221 + if (keySettings.useCellStyleFunction === true) {
  222 + if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {
  223 + try {
  224 + cellStyleFunction = new Function('value', keySettings.cellStyleFunction);
  225 + useCellStyleFunction = true;
  226 + } catch (e) {
  227 + cellStyleFunction = null;
  228 + useCellStyleFunction = false;
  229 + }
  230 + }
  231 + }
  232 +
  233 + vm.stylesInfo[dataKey.label] = {
  234 + useCellStyleFunction: useCellStyleFunction,
  235 + cellStyleFunction: cellStyleFunction
  236 + };
  237 +
  238 + row.data.push(dataKey);
  239 + }
  240 + vm.rows.push(row);
  241 + }
  242 + }
  243 + }
  244 + }
  245 +
  246 + function updateRowData(data) {
  247 + var dataIndex = 0;
  248 + for (var r = 0; r < vm.rows.length; r++) {
  249 + var row = vm.rows[r];
  250 + for (var d = 0; d < row.data.length; d++ ) {
  251 + var keyData = data[dataIndex++].data;
  252 + if (keyData && keyData.length && keyData[0].length > 1) {
  253 + row.data[d].currentValue = row.data[d].originalValue = keyData[0][1];
  254 + }
  255 + }
  256 + }
  257 + }
  258 +
  259 +}
  1 +/**
  2 + * Copyright © 2016-2019 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 +.tb-multiple-input {
  17 + height: 100%;
  18 +
  19 + .md-button.md-icon-button {
  20 + width: 32px;
  21 + min-width: 32px;
  22 + height: 32px;
  23 + min-height: 32px;
  24 + padding: 0 !important;
  25 + margin: 0;
  26 + line-height: 20px;
  27 + }
  28 +
  29 + .md-icon-button md-icon {
  30 + width: 20px;
  31 + min-width: 20px;
  32 + height: 20px;
  33 + min-height: 20px;
  34 + font-size: 20px;
  35 +
  36 + &:not([disabled]) {
  37 + color: #f66;
  38 + }
  39 + }
  40 +}
  41 +
  42 +md-toast {
  43 + min-width: 0;
  44 +
  45 + .md-toast-content {
  46 + font-size: 14px !important;
  47 + }
  48 +}
  49 +
  50 +.footer {
  51 + position: absolute;
  52 + bottom: 0;
  53 + left: 0;
  54 + width: 100%;
  55 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 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 +<form class="tb-multiple-input" name="multipleInputForm" ng-submit="vm.postData($event)">
  19 + <div style="padding: 0 8px; margin: auto 0;">
  20 + <div ng-show="vm.entityDetected" layout="row" flex ng-repeat="row in vm.rows track by $index">
  21 + <div layout="column" flex ng-repeat="key in row.data track by $index">
  22 + <md-input-container class="md-icon-float" ng-style="vm.cellStyle(key)">
  23 + <label>{{key.label}}</label>
  24 + <md-icon class="material-icons" ng-if="key.settings.icon">
  25 + {{key.settings.icon}}
  26 + </md-icon>
  27 + <input name="key.name"
  28 + ng-model="key.currentValue"
  29 + type="{{key.settings.inputType}}"
  30 + step="{{key.settings.step}}"
  31 + md-select-on-focus
  32 + ng-change="vm.inputChanged()">
  33 + </md-input-container>
  34 + </div>
  35 + </div>
  36 +
  37 + <div style="text-align: center; font-size: 18px; color: #a0a0a0;" ng-hide="vm.entityDetected" ng-bind="vm.message"
  38 + ></div>
  39 + <div style="text-align: center; font-size: 18px; color: #a0a0a0;" ng-show="vm.entityDetected && !vm.dataKeyDetected">
  40 + No attribute is selected
  41 + </div>
  42 + <div style="text-align: center; font-size: 18px; color: #a0a0a0;" ng-show="vm.entityDetected && !vm.isValidParameter">
  43 + Timeseries parameter cannot be used in this widget
  44 + </div>
  45 + </div>
  46 + <div class="footer md-padding" layout="row" layout-align="end center" ng-show="vm.entityDetected && vm.dataKeyDetected">
  47 + <md-button class="md-primary" ng-click="vm.discardAll()" style="max-height: 50px;margin-right:20px;" ng-disabled="!vm.hasAnyChange">
  48 + {{ 'action.discard-changes' | translate }}
  49 + </md-button>
  50 + <md-button class="md-raised md-primary" type="submit" value="Submit" style="max-height: 50px;margin-right:20px;"
  51 + ng-disabled="!vm.hasAnyChange" ng-click="vm.isFocused = false">
  52 + {{ 'action.save' | translate }}
  53 + </md-button>
  54 + </div>
  55 +</form>