Commit 8e90c903bb2ce29efbf9d734402af7d6bf2ecfb4

Authored by Igor Kulikov
1 parent 25af06f2

TB-64: Implement widget actions table.

Showing 29 changed files with 977 additions and 26 deletions
... ... @@ -111,6 +111,7 @@
111 111 "ngtemplate-loader": "^1.3.1",
112 112 "node-sass": "^3.9.3",
113 113 "postcss-loader": "^0.13.0",
  114 + "raw-loader": "^0.5.1",
114 115 "react-hot-loader": "^3.0.0-beta.6",
115 116 "sass-loader": "^4.0.2",
116 117 "style-loader": "^0.13.1",
... ...
... ... @@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo
40 40 .name;
41 41
42 42 /*@ngInject*/
43   -function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {
  43 +function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $translate, types, utils) {
44 44
45 45 $window.$ = $;
46 46 $window.jQuery = $;
... ... @@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
548 548 ' }\n\n' +
549 549
550 550 ' self.typeParameters = function() {\n\n' +
551   - {
552   - useCustomDatasources: false,
553   - maxDatasources: -1 //unlimited
554   - maxDataKeys: -1 //unlimited
555   - }
  551 + return {
  552 + useCustomDatasources: false,
  553 + maxDatasources: -1 //unlimited
  554 + maxDataKeys: -1 //unlimited
  555 + };
556 556 ' }\n\n' +
557 557
  558 + ' self.actionSources = function() {\n\n' +
  559 + return {
  560 + 'headerButton': {
  561 + name: 'Header button',
  562 + multiple: true
  563 + }
  564 + };
  565 + }\n\n' +
558 566 ' self.onResize = function() {\n\n' +
559 567
560 568 ' }\n\n' +
... ... @@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
611 619 if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
612 620 result.typeParameters.maxDataKeys = -1;
613 621 }
  622 + if (angular.isFunction(widgetTypeInstance.actionSources)) {
  623 + result.actionSources = widgetTypeInstance.actionSources();
  624 + } else {
  625 + result.actionSources = {};
  626 + }
  627 + for (var actionSourceId in types.widgetActionSources) {
  628 + result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]);
  629 + result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name);
  630 + }
  631 +
614 632 return result;
615 633 } catch (e) {
616 634 utils.processWidgetException(e);
... ... @@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
650 668 widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
651 669 }
652 670 widgetInfo.typeParameters = widgetType.typeParameters;
  671 + widgetInfo.actionSources = widgetType.actionSources;
653 672 putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
654 673 putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
655 674 deferred.resolve(widgetInfo);
... ...
... ... @@ -389,6 +389,30 @@ export default angular.module('thingsboard.types', [])
389 389 }
390 390 }
391 391 },
  392 + widgetActionSources: {
  393 + 'headerButton': {
  394 + name: 'widget-action.header-button',
  395 + multiple: true
  396 + }
  397 + },
  398 + widgetActionTypes: {
  399 + openDashboardState: {
  400 + name: 'widget-action.open-dashboard-state',
  401 + value: 'openDashboardState'
  402 + },
  403 + updateDashboardState: {
  404 + name: 'widget-action.update-dashboard-state',
  405 + value: 'updateDashboardState'
  406 + },
  407 + openDashboard: {
  408 + name: 'widget-action.open-dashboard',
  409 + value: 'openDashboard'
  410 + },
  411 + custom: {
  412 + name: 'widget-action.custom',
  413 + value: 'custom'
  414 + }
  415 + },
392 416 systemBundleAlias: {
393 417 charts: "charts",
394 418 cards: "cards"
... ...
... ... @@ -13,6 +13,13 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
16 23 import tinycolor from "tinycolor2";
17 24 import jsonSchemaDefaults from "json-schema-defaults";
18 25 import thingsboardTypes from "./types.constant";
... ... @@ -24,11 +31,14 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
24 31 const varsRegex = /\$\{([^\}]*)\}/g;
25 32
26 33 /*@ngInject*/
27   -function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
  34 +function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) {
28 35
29 36 var predefinedFunctions = {},
30 37 predefinedFunctionsList = [],
31   - materialColors = [];
  38 + materialColors = [],
  39 + materialIcons = [];
  40 +
  41 + var commonUsedMaterialIcons = [ 'more_horiz', 'close', 'play_arrow' ];
32 42
33 43 predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
34 44 predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
... ... @@ -122,6 +132,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
122 132 getDefaultDatasourceJson: getDefaultDatasourceJson,
123 133 getDefaultAlarmDataKeys: getDefaultAlarmDataKeys,
124 134 getMaterialColor: getMaterialColor,
  135 + getMaterialIcons: getMaterialIcons,
  136 + getCommonMaterialIcons: getCommonMaterialIcons,
125 137 getPredefinedFunctionBody: getPredefinedFunctionBody,
126 138 getPredefinedFunctionsList: getPredefinedFunctionsList,
127 139 genMaterialColor: genMaterialColor,
... ... @@ -154,6 +166,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
154 166 return materialColors[colorIndex].value;
155 167 }
156 168
  169 + function getMaterialIcons() {
  170 + var deferred = $q.defer();
  171 + if (materialIcons.length) {
  172 + deferred.resolve(materialIcons);
  173 + } else {
  174 + $timeout(function() {
  175 + var codepointsArray = materialIconsCodepoints.split("\n");
  176 + codepointsArray.forEach(function (codepoint) {
  177 + if (codepoint && codepoint.length) {
  178 + var values = codepoint.split(' ');
  179 + if (values && values.length == 2) {
  180 + materialIcons.push(values[0]);
  181 + }
  182 + }
  183 + });
  184 + deferred.resolve(materialIcons);
  185 + });
  186 + }
  187 + return deferred.promise;
  188 + }
  189 +
  190 + function getCommonMaterialIcons() {
  191 + return commonUsedMaterialIcons;
  192 + }
  193 +
157 194 function genMaterialColor(str) {
158 195 var hash = Math.abs(hashCode(str));
159 196 return getMaterialColor(hash);
... ...
... ... @@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
20 20 import angularGridster from 'angular-gridster';
21 21 import thingsboardTypes from '../common/types.constant';
22 22 import thingsboardApiWidget from '../api/widget.service';
23   -import thingsboardWidget from './widget.directive';
  23 +import thingsboardWidget from './widget/widget.directive';
24 24 import thingsboardToast from '../services/toast';
25 25 import thingsboardTimewindow from './timewindow.directive';
26 26 import thingsboardEvents from './tb-event-directives';
... ...
  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 +export default angular.module('thingsboard.directives.finishRender', [])
  18 + .directive('tbOnFinishRender', OnFinishRender)
  19 + .name;
  20 +
  21 +/*@ngInject*/
  22 +function OnFinishRender($timeout) {
  23 + return {
  24 + restrict: 'A',
  25 + link: function (scope, element, attr) {
  26 + if (scope.$last === true) {
  27 + $timeout(function () {
  28 + scope.$emit(attr.tbOnFinishRender);
  29 + });
  30 + }
  31 + }
  32 + };
  33 +}
... ...
  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 +import MaterialIconsDialogController from './material-icons-dialog.controller';
  18 +
  19 +/* eslint-disable import/no-unresolved, import/default */
  20 +
  21 +import materialIconSelectTemplate from './material-icon-select.tpl.html';
  22 +import materialIconsDialogTemplate from './material-icons-dialog.tpl.html';
  23 +
  24 +/* eslint-enable import/no-unresolved, import/default */
  25 +
  26 +
  27 +export default angular.module('thingsboard.directives.materialIconSelect', [])
  28 + .controller('MaterialIconsDialogController', MaterialIconsDialogController)
  29 + .directive('tbMaterialIconSelect', MaterialIconSelect)
  30 + .name;
  31 +
  32 +/*@ngInject*/
  33 +function MaterialIconSelect($compile, $templateCache, $document, $mdDialog) {
  34 +
  35 + var linker = function (scope, element, attrs, ngModelCtrl) {
  36 + var template = $templateCache.get(materialIconSelectTemplate);
  37 + element.html(template);
  38 +
  39 + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
  40 + scope.icon = null;
  41 +
  42 + scope.updateView = function () {
  43 + ngModelCtrl.$setViewValue(scope.icon);
  44 + }
  45 +
  46 + ngModelCtrl.$render = function () {
  47 + if (ngModelCtrl.$viewValue) {
  48 + scope.icon = ngModelCtrl.$viewValue;
  49 + }
  50 + if (!scope.icon || !scope.icon.length) {
  51 + scope.icon = 'more_horiz';
  52 + }
  53 + }
  54 +
  55 + scope.$watch('icon', function () {
  56 + scope.updateView();
  57 + });
  58 +
  59 + scope.openIconDialog = function($event) {
  60 + if ($event) {
  61 + $event.stopPropagation();
  62 + }
  63 + $mdDialog.show({
  64 + controller: 'MaterialIconsDialogController',
  65 + controllerAs: 'vm',
  66 + templateUrl: materialIconsDialogTemplate,
  67 + parent: angular.element($document[0].body),
  68 + locals: {icon: scope.icon},
  69 + skipHide: true,
  70 + fullscreen: true,
  71 + targetEvent: $event
  72 + }).then(function (icon) {
  73 + scope.icon = icon;
  74 + });
  75 + }
  76 +
  77 + $compile(element.contents())(scope);
  78 + }
  79 +
  80 + return {
  81 + restrict: "E",
  82 + require: "^ngModel",
  83 + link: linker,
  84 + scope: {
  85 + tbRequired: '=?',
  86 + }
  87 + };
  88 +}
... ...
  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 +<div layout="row">
  19 + <md-icon class="material-icons" ng-click="openIconDialog($event)">{{icon}}</md-icon>
  20 + <md-input-container flex>
  21 + <md-input-container class="md-block">
  22 + <label translate>icon.icon</label>
  23 + <input ng-click="openIconDialog($event)" ng-model="icon">
  24 + </md-input-container>
  25 + </md-input-container>
  26 +</div>
\ No newline at end of file
... ...
  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 +import './material-icons-dialog.scss';
  18 +
  19 +/*@ngInject*/
  20 +export default function MaterialIconsDialogController($scope, $mdDialog, $timeout, utils, icon) {
  21 +
  22 + var vm = this;
  23 +
  24 + vm.selectedIcon = icon;
  25 +
  26 + vm.showAll = false;
  27 + vm.loadingIcons = false;
  28 +
  29 + $scope.$watch('vm.showAll', function(showAll) {
  30 + if (showAll) {
  31 + vm.loadingIcons = true;
  32 + $timeout(function() {
  33 + utils.getMaterialIcons().then(
  34 + function success(icons) {
  35 + vm.icons = icons;
  36 + }
  37 + );
  38 + });
  39 + } else {
  40 + vm.icons = utils.getCommonMaterialIcons();
  41 + }
  42 + });
  43 +
  44 + $scope.$on('iconsLoadFinished', function() {
  45 + vm.loadingIcons = false;
  46 + });
  47 +
  48 + vm.cancel = cancel;
  49 + vm.selectIcon = selectIcon;
  50 +
  51 + function cancel() {
  52 + $mdDialog.cancel();
  53 + }
  54 +
  55 + function selectIcon($event, icon) {
  56 + vm.selectedIcon = icon;
  57 + $mdDialog.hide(vm.selectedIcon);
  58 + }
  59 +}
... ...
  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 +.tb-material-icons-dialog {
  18 + button.md-icon-button.tb-select-icon-button {
  19 + border: solid 1px orange;
  20 + border-radius: 0%;
  21 + padding: 16px;
  22 + height: 56px;
  23 + width: 56px;
  24 + margin: 10px;
  25 + }
  26 + .tb-icons-load {
  27 + top: 64px;
  28 + background: rgba(255,255,255,0.75);
  29 + z-index: 3;
  30 + }
  31 +}
\ 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 +<md-dialog class="tb-material-icons-dialog" aria-label="{{'icon.material-icons' | translate }}" style="min-width: 600px;">
  19 + <form>
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2>{{ 'icon.select-icon' | translate }}</h2>
  23 + <span flex></span>
  24 + <section layout="row" layout-align="start center">
  25 + <md-switch ng-model="vm.showAll"
  26 + aria-label="{{ 'icon.show-all' | translate }}">
  27 + </md-switch>
  28 + <label translate>icon.show-all</label>
  29 + </section>
  30 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  31 + <ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
  32 + </md-button>
  33 + </div>
  34 + </md-toolbar>
  35 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  36 + <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  37 + <div class="tb-absolute-fill tb-icons-load" ng-show="vm.loadingIcons" layout="column" layout-align="center center">
  38 + <md-progress-circular md-mode="indeterminate" ng-disabled="!vm.loadingIcons" class="md-accent" md-diameter="40"></md-progress-circular>
  39 + </div>
  40 + <md-dialog-content>
  41 + <div class="md-dialog-content">
  42 + <md-content class="md-padding" layout="column">
  43 + <fieldset ng-disabled="loading">
  44 + <md-button ng-class="{'md-primary md-raised': icon == vm.selectedIcon}" class="tb-select-icon-button md-icon-button"
  45 + ng-repeat="icon in vm.icons" ng-click="vm.selectIcon($event, icon)" tb-on-finish-render="iconsLoadFinished">
  46 + <md-icon class="material-icons">{{icon}}</md-icon>
  47 + <md-tooltip md-direction="bottom">
  48 + {{ icon }}
  49 + </md-tooltip>
  50 + </md-button>
  51 + </fieldset>
  52 + </md-content>
  53 + </div>
  54 + </md-dialog-content>
  55 + <md-dialog-actions layout="row">
  56 + <span flex></span>
  57 + <md-button ng-disabled="loading" ng-click="vm.cancel()">
  58 + {{ 'action.cancel' | translate }}
  59 + </md-button>
  60 + </md-dialog-actions>
  61 + </form>
  62 +</md-dialog>
... ...
  1 +/**
  2 + * Created by igor on 6/20/17.
  3 + */
  4 +/*
  5 + * Copyright © 2016-2017 The Thingsboard Authors
  6 + *
  7 + * Licensed under the Apache License, Version 2.0 (the "License");
  8 + * you may not use this file except in compliance with the License.
  9 + * You may obtain a copy of the License at
  10 + *
  11 + * http://www.apache.org/licenses/LICENSE-2.0
  12 + *
  13 + * Unless required by applicable law or agreed to in writing, software
  14 + * distributed under the License is distributed on an "AS IS" BASIS,
  15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16 + * See the License for the specific language governing permissions and
  17 + * limitations under the License.
  18 + */
  19 +
  20 +import './manage-widget-actions.scss';
  21 +
  22 +import thingsboardMaterialIconSelect from '../../material-icon-select.directive';
  23 +
  24 +import WidgetActionDialogController from './widget-action-dialog.controller';
  25 +
  26 +/* eslint-disable import/no-unresolved, import/default */
  27 +
  28 +import manageWidgetActionsTemplate from './manage-widget-actions.tpl.html';
  29 +import widgetActionDialogTemplate from './widget-action-dialog.tpl.html';
  30 +
  31 +/* eslint-enable import/no-unresolved, import/default */
  32 +
  33 +export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect])
  34 + .controller('WidgetActionDialogController', WidgetActionDialogController)
  35 + .directive('tbManageWidgetActions', ManageWidgetActions)
  36 + .name;
  37 +
  38 +/*@ngInject*/
  39 +function ManageWidgetActions() {
  40 + return {
  41 + restrict: "E",
  42 + scope: true,
  43 + bindToController: {
  44 + actionSources: '=',
  45 + widgetActions: '='
  46 + },
  47 + controller: ManageWidgetActionsController,
  48 + controllerAs: 'vm',
  49 + templateUrl: manageWidgetActionsTemplate
  50 + };
  51 +}
  52 +
  53 +/* eslint-disable angular/angularelement */
  54 +
  55 +
  56 +/*@ngInject*/
  57 +function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter,
  58 + $translate, $timeout, types) {
  59 +
  60 + let vm = this;
  61 +
  62 + vm.allActions = [];
  63 +
  64 + vm.actions = [];
  65 + vm.actionsCount = 0;
  66 +
  67 + vm.query = {
  68 + order: 'actionSourceName',
  69 + limit: 10,
  70 + page: 1,
  71 + search: null
  72 + };
  73 +
  74 + vm.enterFilterMode = enterFilterMode;
  75 + vm.exitFilterMode = exitFilterMode;
  76 + vm.onReorder = onReorder;
  77 + vm.onPaginate = onPaginate;
  78 + vm.addAction = addAction;
  79 + vm.editAction = editAction;
  80 + vm.deleteAction = deleteAction;
  81 +
  82 + $timeout(function(){
  83 + $scope.manageWidgetActionsForm.querySearchInput.$pristine = false;
  84 + });
  85 +
  86 + $scope.$watch('vm.widgetActions', function() {
  87 + if (vm.widgetActions) {
  88 + reloadActions();
  89 + }
  90 + });
  91 +
  92 + $scope.$watch("vm.query.search", function(newVal, prevVal) {
  93 + if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
  94 + updateActions();
  95 + }
  96 + });
  97 +
  98 + function enterFilterMode () {
  99 + vm.query.search = '';
  100 + }
  101 +
  102 + function exitFilterMode () {
  103 + vm.query.search = null;
  104 + updateActions();
  105 + }
  106 +
  107 + function onReorder () {
  108 + updateActions();
  109 + }
  110 +
  111 + function onPaginate () {
  112 + updateActions();
  113 + }
  114 +
  115 + function addAction($event) {
  116 + if ($event) {
  117 + $event.stopPropagation();
  118 + }
  119 + openWidgetActionDialog($event, null, true);
  120 + }
  121 +
  122 + function editAction ($event, action) {
  123 + if ($event) {
  124 + $event.stopPropagation();
  125 + }
  126 + openWidgetActionDialog($event, action, false);
  127 + }
  128 +
  129 + function deleteAction($event, action) {
  130 + if ($event) {
  131 + $event.stopPropagation();
  132 + }
  133 + if (action) {
  134 + var title = $translate.instant('widget-config.delete-action-title');
  135 + var content = $translate.instant('widget-config.delete-action-text', {actionName: action.name});
  136 + var confirm = $mdDialog.confirm()
  137 + .targetEvent($event)
  138 + .title(title)
  139 + .htmlContent(content)
  140 + .ariaLabel(title)
  141 + .cancel($translate.instant('action.no'))
  142 + .ok($translate.instant('action.yes'));
  143 +
  144 + confirm._options.skipHide = true;
  145 + confirm._options.fullscreen = true;
  146 +
  147 + $mdDialog.show(confirm).then(function () {
  148 + var index = getActionIndex(action.id, vm.allActions);
  149 + if (index > -1) {
  150 + vm.allActions.splice(index, 1);
  151 + }
  152 + var targetActions = vm.widgetActions[action.actionSourceId];
  153 + index = getActionIndex(action.id, targetActions);
  154 + if (index > -1) {
  155 + targetActions.splice(index, 1);
  156 + }
  157 + $scope.manageWidgetActionsForm.$setDirty();
  158 + updateActions();
  159 + });
  160 + }
  161 + }
  162 +
  163 + function openWidgetActionDialog($event, action, isAdd) {
  164 + var prevActionId = null;
  165 + if (!isAdd) {
  166 + prevActionId = action.id;
  167 + }
  168 + $mdDialog.show({
  169 + controller: 'WidgetActionDialogController',
  170 + controllerAs: 'vm',
  171 + templateUrl: widgetActionDialogTemplate,
  172 + parent: angular.element($document[0].body),
  173 + locals: {isAdd: isAdd, actionSources: vm.actionSources, action: angular.copy(action)},
  174 + skipHide: true,
  175 + fullscreen: true,
  176 + targetEvent: $event
  177 + }).then(function (action) {
  178 + saveAction(action, prevActionId);
  179 + updateActions();
  180 + });
  181 + }
  182 +
  183 + function getActionIndex(id, actions) {
  184 + var result = $filter('filter')(actions, {id: id}, true);
  185 + if (result && result.length) {
  186 + return actions.indexOf(result[0]);
  187 + }
  188 + return -1;
  189 + }
  190 +
  191 + function saveAction(action, prevActionId) {
  192 + action.actionSourceName = vm.actionSources[action.actionSourceId].name;
  193 + action.typeName = $translate.instant(types.widgetActionTypes[action.type].name);
  194 + var actionSourceId = action.actionSourceId;
  195 + var widgetAction = angular.copy(action);
  196 + delete widgetAction.actionSourceId;
  197 + delete widgetAction.actionSourceName;
  198 + delete widgetAction.typeName;
  199 + var targetActions = vm.widgetActions[actionSourceId];
  200 + if (!targetActions) {
  201 + targetActions = [];
  202 + vm.widgetActions[actionSourceId] = targetActions;
  203 + }
  204 + if (prevActionId) {
  205 + var index = getActionIndex(prevActionId, vm.allActions);
  206 + if (index > -1) {
  207 + vm.allActions[index] = action;
  208 + }
  209 + index = getActionIndex(prevActionId, targetActions);
  210 + if (index > -1) {
  211 + targetActions[index] = widgetAction;
  212 + }
  213 + } else {
  214 + vm.allActions.push(action);
  215 + targetActions.push(widgetAction);
  216 + }
  217 + $scope.manageWidgetActionsForm.$setDirty();
  218 + }
  219 +
  220 + function reloadActions() {
  221 + vm.allActions = [];
  222 + vm.actions = [];
  223 + vm.actionsCount = 0;
  224 +
  225 + for (var actionSourceId in vm.widgetActions) {
  226 + var actionSource = vm.actionSources[actionSourceId];
  227 + var actionSourceActions = vm.widgetActions[actionSourceId];
  228 + for (var i=0;i<actionSourceActions.length;i++) {
  229 + var actionSourceAction = actionSourceActions[i];
  230 + var action = {
  231 + id: actionSourceAction.id,
  232 + actionSourceId: actionSourceId,
  233 + actionSourceName: actionSource.name,
  234 + name: actionSourceAction.name,
  235 + icon: actionSourceAction.icon,
  236 + type: actionSourceAction.type,
  237 + typeName: $translate.instant(types.widgetActionTypes[actionSourceAction.type].name)
  238 + };
  239 + vm.allActions.push(action);
  240 + }
  241 + }
  242 +
  243 + updateActions ();
  244 + }
  245 +
  246 + function updateActions () {
  247 + var result = $filter('orderBy')(vm.allActions, vm.query.order);
  248 + if (vm.query.search != null) {
  249 + result = $filter('filter')(result, {$: vm.query.search});
  250 + }
  251 + vm.actionsCount = result.length;
  252 + var startIndex = vm.query.limit * (vm.query.page - 1);
  253 + vm.actions = result.slice(startIndex, startIndex + vm.query.limit);
  254 + }
  255 +}
\ No newline at end of file
... ...
  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 +.tb-manage-widget-actions {
  17 + table.md-table {
  18 + tbody {
  19 + tr {
  20 + td {
  21 + &.tb-action-cell {
  22 + overflow: hidden;
  23 + text-overflow: ellipsis;
  24 + white-space: nowrap;
  25 + min-width: 100px;
  26 + max-width: 100px;
  27 + width: 100px;
  28 + }
  29 + }
  30 + }
  31 + }
  32 + }
  33 +}
... ...
  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 +<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1" layout="column">
  19 + <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null">
  20 + <div class="md-toolbar-tools">
  21 + <span translate>widget-config.actions</span>
  22 + <span flex></span>
  23 + <md-button class="md-icon-button" ng-click="vm.addAction($event)">
  24 + <md-icon>add</md-icon>
  25 + <md-tooltip md-direction="top">
  26 + {{ 'widget-config.add-action' | translate }}
  27 + </md-tooltip>
  28 + </md-button>
  29 + <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
  30 + <md-icon>search</md-icon>
  31 + <md-tooltip md-direction="top">
  32 + {{ 'action.search' | translate }}
  33 + </md-tooltip>
  34 + </md-button>
  35 + </div>
  36 + </md-toolbar>
  37 + <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
  38 + <div class="md-toolbar-tools">
  39 + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
  40 + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
  41 + <md-tooltip md-direction="top">
  42 + {{ 'widget-config.search-actions' | translate }}
  43 + </md-tooltip>
  44 + </md-button>
  45 + <md-input-container flex>
  46 + <label>&nbsp;</label>
  47 + <input ng-model="vm.query.search" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/>
  48 + </md-input-container>
  49 + <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
  50 + <md-icon aria-label="Close" class="material-icons">close</md-icon>
  51 + <md-tooltip md-direction="top">
  52 + {{ 'action.close' | translate }}
  53 + </md-tooltip>
  54 + </md-button>
  55 + </div>
  56 + </md-toolbar>
  57 + <md-table-container>
  58 + <table md-table>
  59 + <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
  60 + <tr md-row>
  61 + <th md-column md-order-by="actionSourceName"><span translate>widget-config.action-source</span></th>
  62 + <th md-column md-order-by="name"><span translate>widget-config.action-name</span></th>
  63 + <th md-column md-order-by="icon"><span translate>widget-config.action-icon</span></th>
  64 + <th md-column md-order-by="typeName"><span translate>widget-config.action-type</span></th>
  65 + <th md-column><span>&nbsp</span></th>
  66 + </tr>
  67 + </thead>
  68 + <tbody md-body>
  69 + <tr md-row ng-repeat="action in vm.actions">
  70 + <td md-cell>{{action.actionSourceName}}</td>
  71 + <td md-cell>{{action.name}}</td>
  72 + <td md-cell>
  73 + <md-icon aria-label="{{ 'widget-config.action-icon' | translate }}" class="material-icons">{{action.icon}}</md-icon>
  74 + </td>
  75 + <td md-cell>{{action.typeName}}</td>
  76 + <td md-cell class="tb-action-cell">
  77 + <md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}"
  78 + ng-click="vm.editAction($event, action)">
  79 + <md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon>
  80 + <md-tooltip md-direction="top">
  81 + {{ 'widget-config.edit-action' | translate }}
  82 + </md-tooltip>
  83 + </md-button>
  84 + <md-button class="md-icon-button" aria-label="{{'action.delete' | translate}}" ng-click="vm.deleteAction($event, action)">
  85 + <md-icon aria-label="Delete" class="material-icons">delete</md-icon>
  86 + <md-tooltip md-direction="top">
  87 + {{ 'widget-config.delete-action' | translate }}
  88 + </md-tooltip>
  89 + </md-button>
  90 + </td>
  91 + </tr>
  92 + </tbody>
  93 + </table>
  94 + </md-table-container>
  95 + <md-table-pagination md-limit="vm.query.limit" md-limit-options="[10, 15, 20]"
  96 + md-page="vm.query.page" md-total="{{vm.actionsCount}}"
  97 + md-on-paginate="vm.onPaginate" md-page-select>
  98 + </md-table-pagination>
  99 +</div>
... ...
  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 +/*@ngInject*/
  18 +export default function WidgetActionDialogController($scope, $mdDialog, types, utils, isAdd, actionSources, action) {
  19 +
  20 + var vm = this;
  21 +
  22 + vm.types = types;
  23 +
  24 + vm.isAdd = isAdd;
  25 + vm.actionSources = actionSources;
  26 +
  27 + if (vm.isAdd) {
  28 + vm.action = {
  29 + id: utils.guid()
  30 + };
  31 + } else {
  32 + vm.action = action;
  33 + }
  34 +
  35 + vm.cancel = cancel;
  36 + vm.save = save;
  37 +
  38 + function cancel() {
  39 + $mdDialog.cancel();
  40 + }
  41 +
  42 + function save() {
  43 + $scope.theForm.$setPristine();
  44 + $mdDialog.hide(vm.action);
  45 + }
  46 +}
... ...
  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 +<md-dialog class="tb-widget-action-dialog" aria-label="{{'widget-config.action' | translate }}" style="min-width: 600px;">
  19 + <form name="theForm" ng-submit="vm.save()">
  20 + <md-toolbar>
  21 + <div class="md-toolbar-tools">
  22 + <h2>{{ (vm.isAdd ? 'widget-config.add-action' : 'widget-config.edit-action') | translate }}</h2>
  23 + <span flex></span>
  24 + <md-button class="md-icon-button" ng-click="vm.cancel()">
  25 + <ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon>
  26 + </md-button>
  27 + </div>
  28 + </md-toolbar>
  29 + <md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear>
  30 + <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
  31 + <md-dialog-content>
  32 + <div class="md-dialog-content">
  33 + <md-content class="md-padding" layout="column">
  34 + <fieldset ng-disabled="loading">
  35 + <md-input-container class="md-block">
  36 + <label translate>widget-config.action-source</label>
  37 + <md-select name="actionSource" required aria-label="{{ 'widget-config.action-source' | translate }}" ng-model="vm.action.actionSourceId">
  38 + <md-option ng-repeat="(actionSourceId, actionSource) in vm.actionSources" ng-value="actionSourceId">
  39 + {{actionSource.name}}
  40 + </md-option>
  41 + </md-select>
  42 + <div ng-messages="theForm.actionSource.$error">
  43 + <div ng-message="required" translate>widget-config.action-source-required</div>
  44 + </div>
  45 + </md-input-container>
  46 + <md-input-container class="md-block">
  47 + <label translate>widget-config.action-name</label>
  48 + <input name="name" required ng-model="vm.action.name">
  49 + <div ng-messages="theForm.name.$error">
  50 + <div ng-message="required" translate>widget-config.action-name-required</div>
  51 + </div>
  52 + </md-input-container>
  53 + <tb-material-icon-select ng-model="vm.action.icon">
  54 + </tb-material-icon-select>
  55 + <md-input-container class="md-block">
  56 + <label translate>widget-config.action-type</label>
  57 + <md-select name="actionType" required aria-label="{{ 'widget-config.action-type' | translate }}" ng-model="vm.action.type">
  58 + <md-option ng-repeat="actionType in vm.types.widgetActionTypes" ng-value="actionType.value">
  59 + {{ actionType.name | translate }}
  60 + </md-option>
  61 + </md-select>
  62 + <div ng-messages="theForm.actionType.$error">
  63 + <div ng-message="required" translate>widget-config.action-type-required</div>
  64 + </div>
  65 + </md-input-container>
  66 + </fieldset>
  67 + </md-content>
  68 + </div>
  69 + </md-dialog-content>
  70 + <md-dialog-actions layout="row">
  71 + <span flex></span>
  72 + <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
  73 + class="md-raised md-primary">
  74 + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
  75 + </md-button>
  76 + <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
  77 + {{ 'action.cancel' | translate }}
  78 + </md-button>
  79 + </md-dialog-actions>
  80 + </form>
  81 +</md-dialog>
... ...
ui/src/app/components/widget/widget-config.directive.js renamed from ui/src/app/components/widget-config.directive.js
... ... @@ -14,13 +14,14 @@
14 14 * limitations under the License.
15 15 */
16 16 import jsonSchemaDefaults from 'json-schema-defaults';
17   -import thingsboardTypes from '../common/types.constant';
18   -import thingsboardUtils from '../common/utils.service';
19   -import thingsboardEntityAliasSelect from './entity-alias-select.directive';
20   -import thingsboardDatasource from './datasource.directive';
21   -import thingsboardTimewindow from './timewindow.directive';
22   -import thingsboardLegendConfig from './legend-config.directive';
23   -import thingsboardJsonForm from "./json-form.directive";
  17 +import thingsboardTypes from '../../common/types.constant';
  18 +import thingsboardUtils from '../../common/utils.service';
  19 +import thingsboardEntityAliasSelect from '../entity-alias-select.directive';
  20 +import thingsboardDatasource from '../datasource.directive';
  21 +import thingsboardTimewindow from '../timewindow.directive';
  22 +import thingsboardLegendConfig from '../legend-config.directive';
  23 +import thingsboardJsonForm from '../json-form.directive';
  24 +import thingsboardManageWidgetActions from './action/manage-widget-actions.directive';
24 25 import 'angular-ui-ace';
25 26
26 27 /* eslint-disable import/no-unresolved, import/default */
... ... @@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar
38 39 thingsboardDatasource,
39 40 thingsboardTimewindow,
40 41 thingsboardLegendConfig,
  42 + thingsboardManageWidgetActions,
41 43 'ui.ace'])
42 44 .directive('tbWidgetConfig', WidgetConfig)
43 45 .name;
... ... @@ -117,6 +119,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
117 119 scope.showLegend = angular.isDefined(config.showLegend) ?
118 120 config.showLegend : scope.widgetType === types.widgetType.timeseries.value;
119 121 scope.legendConfig = config.legendConfig;
  122 + scope.actions = config.actions;
  123 + if (!scope.actions) {
  124 + scope.actions = {};
  125 + }
120 126 if (scope.widgetType !== types.widgetType.rpc.value &&
121 127 scope.widgetType !== types.widgetType.alarm.value &&
122 128 scope.widgetType !== types.widgetType.static.value
... ... @@ -324,6 +330,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
324 330 }
325 331 });
326 332
  333 + scope.$watch('actions', function () {
  334 + if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config) {
  335 + var value = ngModelCtrl.$viewValue;
  336 + var config = value.config;
  337 + config.actions = scope.actions;
  338 + ngModelCtrl.$setViewValue(value);
  339 + scope.updateValidity();
  340 + /*if (scope.theForm) {
  341 + scope.theForm.$setDirty();
  342 + }*/
  343 + }
  344 + }, true);
  345 +
327 346 scope.addDatasource = function () {
328 347 var newDatasource;
329 348 if (scope.functionsOnly) {
... ... @@ -443,6 +462,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
443 462 isDataEnabled: '=?',
444 463 widgetType: '=',
445 464 typeParameters: '=',
  465 + actionSources: '=',
446 466 widgetSettingsSchema: '=',
447 467 datakeySettingsSchema: '=',
448 468 aliasController: '=',
... ...
ui/src/app/components/widget/widget-config.tpl.html renamed from ui/src/app/components/widget-config.tpl.html
... ... @@ -275,4 +275,10 @@
275 275 </ng-form>
276 276 </md-content>
277 277 </md-tab>
  278 + <md-tab label="{{ 'widget-config.actions' | translate }}">
  279 + <md-content class="md-padding" layout="column">
  280 + <tb-manage-widget-actions action-sources="actionSources" widget-actions="actions">
  281 + </tb-manage-widget-actions>
  282 + </md-content>
  283 + </md-tab>
278 284 </md-tabs>
... ...
ui/src/app/components/widget/widget.controller.js renamed from ui/src/app/components/widget.controller.js
... ... @@ -15,7 +15,7 @@
15 15 */
16 16 import $ from 'jquery';
17 17 import 'javascript-detect-element-resize/detect-element-resize';
18   -import Subscription from '../api/subscription';
  18 +import Subscription from '../../api/subscription';
19 19
20 20 /* eslint-disable angular/angularelement */
21 21
... ...
ui/src/app/components/widget/widget.directive.js renamed from ui/src/app/components/widget.directive.js
... ... @@ -16,9 +16,9 @@
16 16
17 17 import './widget.scss';
18 18
19   -import thingsboardLegend from './legend.directive';
20   -import thingsboardTypes from '../common/types.constant';
21   -import thingsboardApiDatasource from '../api/datasource.service';
  19 +import thingsboardLegend from '../legend.directive';
  20 +import thingsboardTypes from '../../common/types.constant';
  21 +import thingsboardApiDatasource from '../../api/datasource.service';
22 22
23 23 import WidgetController from './widget.controller';
24 24
... ...
ui/src/app/components/widget/widget.scss renamed from ui/src/app/components/widget.scss
... ... @@ -34,6 +34,7 @@
34 34 <fieldset ng-disabled="loading" style="position: relative; height: 600px;">
35 35 <tb-widget-config widget-type="vm.widget.type"
36 36 type-parameters="vm.widgetInfo.typeParameters"
  37 + action-sources="vm.widgetInfo.actionSources"
37 38 force-expand-datasources="true"
38 39 ng-model="vm.widgetConfig"
39 40 widget-settings-schema="vm.settingsSchema"
... ...
... ... @@ -41,6 +41,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
41 41 var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
42 42 var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
43 43 scope.typeParameters = widgetInfo.typeParameters;
  44 + scope.actionSources = widgetInfo.actionSources;
44 45 scope.isDataEnabled = !widgetInfo.typeParameters.useCustomDatasources;
45 46 if (!settingsSchema || settingsSchema === '') {
46 47 scope.settingsSchema = {};
... ...
... ... @@ -18,6 +18,7 @@
18 18 <fieldset ng-disabled="loading">
19 19 <tb-widget-config widget-type="widget.type"
20 20 type-parameters="typeParameters"
  21 + action-sources="actionSources"
21 22 ng-model="widgetConfig"
22 23 is-data-enabled="isDataEnabled"
23 24 widget-settings-schema="settingsSchema"
... ...
... ... @@ -23,7 +23,7 @@ import thingsboardApiUser from '../api/user.service';
23 23 import thingsboardApiDashboard from '../api/dashboard.service';
24 24 import thingsboardApiCustomer from '../api/customer.service';
25 25 import thingsboardDetailsSidenav from '../components/details-sidenav.directive';
26   -import thingsboardWidgetConfig from '../components/widget-config.directive';
  26 +import thingsboardWidgetConfig from '../components/widget/widget-config.directive';
27 27 import thingsboardDashboardSelect from '../components/dashboard-select.directive';
28 28 import thingsboardRelatedEntityAutocomplete from '../components/related-entity-autocomplete.directive';
29 29 import thingsboardDashboard from '../components/dashboard.directive';
... ...
... ... @@ -59,10 +59,10 @@
59 59 <span flex></span>
60 60 <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
61 61 class="md-raised md-primary">
62   - {{ vm.isAdd ? 'Add' : 'Save' }}
  62 + {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }}
63 63 </md-button>
64 64 <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
65   - Cancel
  65 + {{ 'action.cancel' | translate }}
66 66 </md-button>
67 67 </md-dialog-actions>
68 68 </form>
... ...
... ... @@ -175,8 +175,6 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt
175 175 $scope.theForm.$setDirty();
176 176 updateStates();
177 177 });
178   -
179   -
180 178 }
181 179 }
182 180
... ...
... ... @@ -26,6 +26,7 @@ import thingsboardApiLogin from '../api/login.service';
26 26 import thingsboardApiUser from '../api/user.service';
27 27
28 28 import thingsboardNoAnimate from '../components/no-animate.directive';
  29 +import thingsboardOnFinishRender from '../components/finish-render.directive';
29 30 import thingsboardSideMenu from '../components/side-menu.directive';
30 31 import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
31 32
... ... @@ -81,6 +82,7 @@ export default angular.module('thingsboard.home', [
81 82 thingsboardApiLogin,
82 83 thingsboardApiUser,
83 84 thingsboardNoAnimate,
  85 + thingsboardOnFinishRender,
84 86 thingsboardSideMenu,
85 87 thingsboardDashboardAutocomplete
86 88 ])
... ...
... ... @@ -1103,6 +1103,13 @@ export default angular.module('thingsboard.locale', [])
1103 1103 "undo": "Undo widget changes",
1104 1104 "export": "Export widget"
1105 1105 },
  1106 + "widget-action": {
  1107 + "header-button": "Header button",
  1108 + "open-dashboard-state": "Navigate to new dashboard state",
  1109 + "update-dashboard-state": "Update current dashboard state",
  1110 + "open-dashboard": "Navigate to other dashboard",
  1111 + "custom": "Custom action"
  1112 + },
1106 1113 "widgets-bundle": {
1107 1114 "current": "Current bundle",
1108 1115 "widgets-bundles": "Widgets Bundles",
... ... @@ -1158,7 +1165,22 @@ export default angular.module('thingsboard.locale', [])
1158 1165 "remove-datasource": "Remove datasource",
1159 1166 "add-datasource": "Add datasource",
1160 1167 "target-device": "Target device",
1161   - "alarm-source": "Alarm source"
  1168 + "alarm-source": "Alarm source",
  1169 + "actions": "Actions",
  1170 + "action": "Action",
  1171 + "add-action": "Add action",
  1172 + "search-actions": "Search actions",
  1173 + "action-source": "Action source",
  1174 + "action-source-required": "Action source is required.",
  1175 + "action-name": "Name",
  1176 + "action-name-required": "Action name is required.",
  1177 + "action-icon": "Icon",
  1178 + "action-type": "Type",
  1179 + "action-type-required": "Action type is required.",
  1180 + "edit-action": "Edit action",
  1181 + "delete-action": "Delete action",
  1182 + "delete-action-title": "Delete widget action",
  1183 + "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?"
1162 1184 },
1163 1185 "widget-type": {
1164 1186 "import": "Import widget type",
... ... @@ -1168,6 +1190,12 @@ export default angular.module('thingsboard.locale', [])
1168 1190 "widget-type-file": "Widget type file",
1169 1191 "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure."
1170 1192 },
  1193 + "icon": {
  1194 + "icon": "Icon",
  1195 + "select-icon": "Select icon",
  1196 + "material-icons": "Material icons",
  1197 + "show-all": "Show all icons"
  1198 + },
1171 1199 "language": {
1172 1200 "language": "Language",
1173 1201 "en_US": "English",
... ...