diff --git a/ui/package.json b/ui/package.json
index 73d7bcd347..6884543d77 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -111,6 +111,7 @@
"ngtemplate-loader": "^1.3.1",
"node-sass": "^3.9.3",
"postcss-loader": "^0.13.0",
+ "raw-loader": "^0.5.1",
"react-hot-loader": "^3.0.0-beta.6",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index f8f27ed932..e23e916cca 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -40,7 +40,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo
.name;
/*@ngInject*/
-function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {
+function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $translate, types, utils) {
$window.$ = $;
$window.jQuery = $;
@@ -548,13 +548,21 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
' }\n\n' +
' self.typeParameters = function() {\n\n' +
- {
- useCustomDatasources: false,
- maxDatasources: -1 //unlimited
- maxDataKeys: -1 //unlimited
- }
+ return {
+ useCustomDatasources: false,
+ maxDatasources: -1 //unlimited
+ maxDataKeys: -1 //unlimited
+ };
' }\n\n' +
+ ' self.actionSources = function() {\n\n' +
+ return {
+ 'headerButton': {
+ name: 'Header button',
+ multiple: true
+ }
+ };
+ }\n\n' +
' self.onResize = function() {\n\n' +
' }\n\n' +
@@ -611,6 +619,16 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
if (angular.isUndefined(result.typeParameters.maxDataKeys)) {
result.typeParameters.maxDataKeys = -1;
}
+ if (angular.isFunction(widgetTypeInstance.actionSources)) {
+ result.actionSources = widgetTypeInstance.actionSources();
+ } else {
+ result.actionSources = {};
+ }
+ for (var actionSourceId in types.widgetActionSources) {
+ result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]);
+ result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name);
+ }
+
return result;
} catch (e) {
utils.processWidgetException(e);
@@ -650,6 +668,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
}
widgetInfo.typeParameters = widgetType.typeParameters;
+ widgetInfo.actionSources = widgetType.actionSources;
putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
deferred.resolve(widgetInfo);
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 81ce0fa4f9..1a51a1af2c 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -389,6 +389,30 @@ export default angular.module('thingsboard.types', [])
}
}
},
+ widgetActionSources: {
+ 'headerButton': {
+ name: 'widget-action.header-button',
+ multiple: true
+ }
+ },
+ widgetActionTypes: {
+ openDashboardState: {
+ name: 'widget-action.open-dashboard-state',
+ value: 'openDashboardState'
+ },
+ updateDashboardState: {
+ name: 'widget-action.update-dashboard-state',
+ value: 'updateDashboardState'
+ },
+ openDashboard: {
+ name: 'widget-action.open-dashboard',
+ value: 'openDashboard'
+ },
+ custom: {
+ name: 'widget-action.custom',
+ value: 'custom'
+ }
+ },
systemBundleAlias: {
charts: "charts",
cards: "cards"
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index a3433c255f..e6d48ae13a 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import materialIconsCodepoints from 'raw-loader!material-design-icons/iconfont/codepoints';
+
+/* eslint-enable import/no-unresolved, import/default */
+
import tinycolor from "tinycolor2";
import jsonSchemaDefaults from "json-schema-defaults";
import thingsboardTypes from "./types.constant";
@@ -24,11 +31,14 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
const varsRegex = /\$\{([^\}]*)\}/g;
/*@ngInject*/
-function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
+function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, types) {
var predefinedFunctions = {},
predefinedFunctionsList = [],
- materialColors = [];
+ materialColors = [],
+ materialIcons = [];
+
+ var commonUsedMaterialIcons = [ 'more_horiz', 'close', 'play_arrow' ];
predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
@@ -122,6 +132,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
getDefaultDatasourceJson: getDefaultDatasourceJson,
getDefaultAlarmDataKeys: getDefaultAlarmDataKeys,
getMaterialColor: getMaterialColor,
+ getMaterialIcons: getMaterialIcons,
+ getCommonMaterialIcons: getCommonMaterialIcons,
getPredefinedFunctionBody: getPredefinedFunctionBody,
getPredefinedFunctionsList: getPredefinedFunctionsList,
genMaterialColor: genMaterialColor,
@@ -154,6 +166,31 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, types) {
return materialColors[colorIndex].value;
}
+ function getMaterialIcons() {
+ var deferred = $q.defer();
+ if (materialIcons.length) {
+ deferred.resolve(materialIcons);
+ } else {
+ $timeout(function() {
+ var codepointsArray = materialIconsCodepoints.split("\n");
+ codepointsArray.forEach(function (codepoint) {
+ if (codepoint && codepoint.length) {
+ var values = codepoint.split(' ');
+ if (values && values.length == 2) {
+ materialIcons.push(values[0]);
+ }
+ }
+ });
+ deferred.resolve(materialIcons);
+ });
+ }
+ return deferred.promise;
+ }
+
+ function getCommonMaterialIcons() {
+ return commonUsedMaterialIcons;
+ }
+
function genMaterialColor(str) {
var hash = Math.abs(hashCode(str));
return getMaterialColor(hash);
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index d1427b6091..e61abcc644 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
import angularGridster from 'angular-gridster';
import thingsboardTypes from '../common/types.constant';
import thingsboardApiWidget from '../api/widget.service';
-import thingsboardWidget from './widget.directive';
+import thingsboardWidget from './widget/widget.directive';
import thingsboardToast from '../services/toast';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardEvents from './tb-event-directives';
diff --git a/ui/src/app/components/finish-render.directive.js b/ui/src/app/components/finish-render.directive.js
new file mode 100644
index 0000000000..9092e2db4d
--- /dev/null
+++ b/ui/src/app/components/finish-render.directive.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default angular.module('thingsboard.directives.finishRender', [])
+ .directive('tbOnFinishRender', OnFinishRender)
+ .name;
+
+/*@ngInject*/
+function OnFinishRender($timeout) {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attr) {
+ if (scope.$last === true) {
+ $timeout(function () {
+ scope.$emit(attr.tbOnFinishRender);
+ });
+ }
+ }
+ };
+}
diff --git a/ui/src/app/components/material-icon-select.directive.js b/ui/src/app/components/material-icon-select.directive.js
new file mode 100644
index 0000000000..45ab2cdd2c
--- /dev/null
+++ b/ui/src/app/components/material-icon-select.directive.js
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MaterialIconsDialogController from './material-icons-dialog.controller';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import materialIconSelectTemplate from './material-icon-select.tpl.html';
+import materialIconsDialogTemplate from './material-icons-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+export default angular.module('thingsboard.directives.materialIconSelect', [])
+ .controller('MaterialIconsDialogController', MaterialIconsDialogController)
+ .directive('tbMaterialIconSelect', MaterialIconSelect)
+ .name;
+
+/*@ngInject*/
+function MaterialIconSelect($compile, $templateCache, $document, $mdDialog) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(materialIconSelectTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.icon = null;
+
+ scope.updateView = function () {
+ ngModelCtrl.$setViewValue(scope.icon);
+ }
+
+ ngModelCtrl.$render = function () {
+ if (ngModelCtrl.$viewValue) {
+ scope.icon = ngModelCtrl.$viewValue;
+ }
+ if (!scope.icon || !scope.icon.length) {
+ scope.icon = 'more_horiz';
+ }
+ }
+
+ scope.$watch('icon', function () {
+ scope.updateView();
+ });
+
+ scope.openIconDialog = function($event) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ $mdDialog.show({
+ controller: 'MaterialIconsDialogController',
+ controllerAs: 'vm',
+ templateUrl: materialIconsDialogTemplate,
+ parent: angular.element($document[0].body),
+ locals: {icon: scope.icon},
+ skipHide: true,
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function (icon) {
+ scope.icon = icon;
+ });
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ tbRequired: '=?',
+ }
+ };
+}
diff --git a/ui/src/app/components/material-icon-select.tpl.html b/ui/src/app/components/material-icon-select.tpl.html
new file mode 100644
index 0000000000..77ef87f2e3
--- /dev/null
+++ b/ui/src/app/components/material-icon-select.tpl.html
@@ -0,0 +1,26 @@
+
+
+ {{icon}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/app/components/material-icons-dialog.controller.js b/ui/src/app/components/material-icons-dialog.controller.js
new file mode 100644
index 0000000000..94b18dccb4
--- /dev/null
+++ b/ui/src/app/components/material-icons-dialog.controller.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './material-icons-dialog.scss';
+
+/*@ngInject*/
+export default function MaterialIconsDialogController($scope, $mdDialog, $timeout, utils, icon) {
+
+ var vm = this;
+
+ vm.selectedIcon = icon;
+
+ vm.showAll = false;
+ vm.loadingIcons = false;
+
+ $scope.$watch('vm.showAll', function(showAll) {
+ if (showAll) {
+ vm.loadingIcons = true;
+ $timeout(function() {
+ utils.getMaterialIcons().then(
+ function success(icons) {
+ vm.icons = icons;
+ }
+ );
+ });
+ } else {
+ vm.icons = utils.getCommonMaterialIcons();
+ }
+ });
+
+ $scope.$on('iconsLoadFinished', function() {
+ vm.loadingIcons = false;
+ });
+
+ vm.cancel = cancel;
+ vm.selectIcon = selectIcon;
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function selectIcon($event, icon) {
+ vm.selectedIcon = icon;
+ $mdDialog.hide(vm.selectedIcon);
+ }
+}
diff --git a/ui/src/app/components/material-icons-dialog.scss b/ui/src/app/components/material-icons-dialog.scss
new file mode 100644
index 0000000000..dbd83951b2
--- /dev/null
+++ b/ui/src/app/components/material-icons-dialog.scss
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-material-icons-dialog {
+ button.md-icon-button.tb-select-icon-button {
+ border: solid 1px orange;
+ border-radius: 0%;
+ padding: 16px;
+ height: 56px;
+ width: 56px;
+ margin: 10px;
+ }
+ .tb-icons-load {
+ top: 64px;
+ background: rgba(255,255,255,0.75);
+ z-index: 3;
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/material-icons-dialog.tpl.html b/ui/src/app/components/material-icons-dialog.tpl.html
new file mode 100644
index 0000000000..8a2eb6537b
--- /dev/null
+++ b/ui/src/app/components/material-icons-dialog.tpl.html
@@ -0,0 +1,62 @@
+
+
+
+
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.directive.js b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
new file mode 100644
index 0000000000..b2737296a8
--- /dev/null
+++ b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
@@ -0,0 +1,255 @@
+/**
+ * Created by igor on 6/20/17.
+ */
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './manage-widget-actions.scss';
+
+import thingsboardMaterialIconSelect from '../../material-icon-select.directive';
+
+import WidgetActionDialogController from './widget-action-dialog.controller';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import manageWidgetActionsTemplate from './manage-widget-actions.tpl.html';
+import widgetActionDialogTemplate from './widget-action-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.widgetActions', [thingsboardMaterialIconSelect])
+ .controller('WidgetActionDialogController', WidgetActionDialogController)
+ .directive('tbManageWidgetActions', ManageWidgetActions)
+ .name;
+
+/*@ngInject*/
+function ManageWidgetActions() {
+ return {
+ restrict: "E",
+ scope: true,
+ bindToController: {
+ actionSources: '=',
+ widgetActions: '='
+ },
+ controller: ManageWidgetActionsController,
+ controllerAs: 'vm',
+ templateUrl: manageWidgetActionsTemplate
+ };
+}
+
+/* eslint-disable angular/angularelement */
+
+
+/*@ngInject*/
+function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter,
+ $translate, $timeout, types) {
+
+ let vm = this;
+
+ vm.allActions = [];
+
+ vm.actions = [];
+ vm.actionsCount = 0;
+
+ vm.query = {
+ order: 'actionSourceName',
+ limit: 10,
+ page: 1,
+ search: null
+ };
+
+ vm.enterFilterMode = enterFilterMode;
+ vm.exitFilterMode = exitFilterMode;
+ vm.onReorder = onReorder;
+ vm.onPaginate = onPaginate;
+ vm.addAction = addAction;
+ vm.editAction = editAction;
+ vm.deleteAction = deleteAction;
+
+ $timeout(function(){
+ $scope.manageWidgetActionsForm.querySearchInput.$pristine = false;
+ });
+
+ $scope.$watch('vm.widgetActions', function() {
+ if (vm.widgetActions) {
+ reloadActions();
+ }
+ });
+
+ $scope.$watch("vm.query.search", function(newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
+ updateActions();
+ }
+ });
+
+ function enterFilterMode () {
+ vm.query.search = '';
+ }
+
+ function exitFilterMode () {
+ vm.query.search = null;
+ updateActions();
+ }
+
+ function onReorder () {
+ updateActions();
+ }
+
+ function onPaginate () {
+ updateActions();
+ }
+
+ function addAction($event) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ openWidgetActionDialog($event, null, true);
+ }
+
+ function editAction ($event, action) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ openWidgetActionDialog($event, action, false);
+ }
+
+ function deleteAction($event, action) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ if (action) {
+ var title = $translate.instant('widget-config.delete-action-title');
+ var content = $translate.instant('widget-config.delete-action-text', {actionName: action.name});
+ var confirm = $mdDialog.confirm()
+ .targetEvent($event)
+ .title(title)
+ .htmlContent(content)
+ .ariaLabel(title)
+ .cancel($translate.instant('action.no'))
+ .ok($translate.instant('action.yes'));
+
+ confirm._options.skipHide = true;
+ confirm._options.fullscreen = true;
+
+ $mdDialog.show(confirm).then(function () {
+ var index = getActionIndex(action.id, vm.allActions);
+ if (index > -1) {
+ vm.allActions.splice(index, 1);
+ }
+ var targetActions = vm.widgetActions[action.actionSourceId];
+ index = getActionIndex(action.id, targetActions);
+ if (index > -1) {
+ targetActions.splice(index, 1);
+ }
+ $scope.manageWidgetActionsForm.$setDirty();
+ updateActions();
+ });
+ }
+ }
+
+ function openWidgetActionDialog($event, action, isAdd) {
+ var prevActionId = null;
+ if (!isAdd) {
+ prevActionId = action.id;
+ }
+ $mdDialog.show({
+ controller: 'WidgetActionDialogController',
+ controllerAs: 'vm',
+ templateUrl: widgetActionDialogTemplate,
+ parent: angular.element($document[0].body),
+ locals: {isAdd: isAdd, actionSources: vm.actionSources, action: angular.copy(action)},
+ skipHide: true,
+ fullscreen: true,
+ targetEvent: $event
+ }).then(function (action) {
+ saveAction(action, prevActionId);
+ updateActions();
+ });
+ }
+
+ function getActionIndex(id, actions) {
+ var result = $filter('filter')(actions, {id: id}, true);
+ if (result && result.length) {
+ return actions.indexOf(result[0]);
+ }
+ return -1;
+ }
+
+ function saveAction(action, prevActionId) {
+ action.actionSourceName = vm.actionSources[action.actionSourceId].name;
+ action.typeName = $translate.instant(types.widgetActionTypes[action.type].name);
+ var actionSourceId = action.actionSourceId;
+ var widgetAction = angular.copy(action);
+ delete widgetAction.actionSourceId;
+ delete widgetAction.actionSourceName;
+ delete widgetAction.typeName;
+ var targetActions = vm.widgetActions[actionSourceId];
+ if (!targetActions) {
+ targetActions = [];
+ vm.widgetActions[actionSourceId] = targetActions;
+ }
+ if (prevActionId) {
+ var index = getActionIndex(prevActionId, vm.allActions);
+ if (index > -1) {
+ vm.allActions[index] = action;
+ }
+ index = getActionIndex(prevActionId, targetActions);
+ if (index > -1) {
+ targetActions[index] = widgetAction;
+ }
+ } else {
+ vm.allActions.push(action);
+ targetActions.push(widgetAction);
+ }
+ $scope.manageWidgetActionsForm.$setDirty();
+ }
+
+ function reloadActions() {
+ vm.allActions = [];
+ vm.actions = [];
+ vm.actionsCount = 0;
+
+ for (var actionSourceId in vm.widgetActions) {
+ var actionSource = vm.actionSources[actionSourceId];
+ var actionSourceActions = vm.widgetActions[actionSourceId];
+ for (var i=0;i
+
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.controller.js b/ui/src/app/components/widget/action/widget-action-dialog.controller.js
new file mode 100644
index 0000000000..948308b591
--- /dev/null
+++ b/ui/src/app/components/widget/action/widget-action-dialog.controller.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*@ngInject*/
+export default function WidgetActionDialogController($scope, $mdDialog, types, utils, isAdd, actionSources, action) {
+
+ var vm = this;
+
+ vm.types = types;
+
+ vm.isAdd = isAdd;
+ vm.actionSources = actionSources;
+
+ if (vm.isAdd) {
+ vm.action = {
+ id: utils.guid()
+ };
+ } else {
+ vm.action = action;
+ }
+
+ vm.cancel = cancel;
+ vm.save = save;
+
+ function cancel() {
+ $mdDialog.cancel();
+ }
+
+ function save() {
+ $scope.theForm.$setPristine();
+ $mdDialog.hide(vm.action);
+ }
+}
diff --git a/ui/src/app/components/widget/action/widget-action-dialog.tpl.html b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html
new file mode 100644
index 0000000000..ccb9d41bd6
--- /dev/null
+++ b/ui/src/app/components/widget/action/widget-action-dialog.tpl.html
@@ -0,0 +1,81 @@
+
+
+
+
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget/widget-config.directive.js
similarity index 93%
rename from ui/src/app/components/widget-config.directive.js
rename to ui/src/app/components/widget/widget-config.directive.js
index 4a083c10a7..3df9263c59 100644
--- a/ui/src/app/components/widget-config.directive.js
+++ b/ui/src/app/components/widget/widget-config.directive.js
@@ -14,13 +14,14 @@
* limitations under the License.
*/
import jsonSchemaDefaults from 'json-schema-defaults';
-import thingsboardTypes from '../common/types.constant';
-import thingsboardUtils from '../common/utils.service';
-import thingsboardEntityAliasSelect from './entity-alias-select.directive';
-import thingsboardDatasource from './datasource.directive';
-import thingsboardTimewindow from './timewindow.directive';
-import thingsboardLegendConfig from './legend-config.directive';
-import thingsboardJsonForm from "./json-form.directive";
+import thingsboardTypes from '../../common/types.constant';
+import thingsboardUtils from '../../common/utils.service';
+import thingsboardEntityAliasSelect from '../entity-alias-select.directive';
+import thingsboardDatasource from '../datasource.directive';
+import thingsboardTimewindow from '../timewindow.directive';
+import thingsboardLegendConfig from '../legend-config.directive';
+import thingsboardJsonForm from '../json-form.directive';
+import thingsboardManageWidgetActions from './action/manage-widget-actions.directive';
import 'angular-ui-ace';
/* eslint-disable import/no-unresolved, import/default */
@@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.widgetConfig', [thingsboar
thingsboardDatasource,
thingsboardTimewindow,
thingsboardLegendConfig,
+ thingsboardManageWidgetActions,
'ui.ace'])
.directive('tbWidgetConfig', WidgetConfig)
.name;
@@ -117,6 +119,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
scope.showLegend = angular.isDefined(config.showLegend) ?
config.showLegend : scope.widgetType === types.widgetType.timeseries.value;
scope.legendConfig = config.legendConfig;
+ scope.actions = config.actions;
+ if (!scope.actions) {
+ scope.actions = {};
+ }
if (scope.widgetType !== types.widgetType.rpc.value &&
scope.widgetType !== types.widgetType.alarm.value &&
scope.widgetType !== types.widgetType.static.value
@@ -324,6 +330,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
}
});
+ scope.$watch('actions', function () {
+ if (ngModelCtrl.$viewValue && ngModelCtrl.$viewValue.config) {
+ var value = ngModelCtrl.$viewValue;
+ var config = value.config;
+ config.actions = scope.actions;
+ ngModelCtrl.$setViewValue(value);
+ scope.updateValidity();
+ /*if (scope.theForm) {
+ scope.theForm.$setDirty();
+ }*/
+ }
+ }, true);
+
scope.addDatasource = function () {
var newDatasource;
if (scope.functionsOnly) {
@@ -443,6 +462,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
isDataEnabled: '=?',
widgetType: '=',
typeParameters: '=',
+ actionSources: '=',
widgetSettingsSchema: '=',
datakeySettingsSchema: '=',
aliasController: '=',
diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget/widget-config.tpl.html
similarity index 98%
rename from ui/src/app/components/widget-config.tpl.html
rename to ui/src/app/components/widget/widget-config.tpl.html
index 14cb27c8cd..2cc264c0d7 100644
--- a/ui/src/app/components/widget-config.tpl.html
+++ b/ui/src/app/components/widget/widget-config.tpl.html
@@ -275,4 +275,10 @@
+
+
+
+
+
+
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget/widget.controller.js
similarity index 99%
rename from ui/src/app/components/widget.controller.js
rename to ui/src/app/components/widget/widget.controller.js
index f2e231fa29..3bda80991a 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget/widget.controller.js
@@ -15,7 +15,7 @@
*/
import $ from 'jquery';
import 'javascript-detect-element-resize/detect-element-resize';
-import Subscription from '../api/subscription';
+import Subscription from '../../api/subscription';
/* eslint-disable angular/angularelement */
diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget/widget.directive.js
similarity index 94%
rename from ui/src/app/components/widget.directive.js
rename to ui/src/app/components/widget/widget.directive.js
index a0f1b83c48..40d5a83976 100644
--- a/ui/src/app/components/widget.directive.js
+++ b/ui/src/app/components/widget/widget.directive.js
@@ -16,9 +16,9 @@
import './widget.scss';
-import thingsboardLegend from './legend.directive';
-import thingsboardTypes from '../common/types.constant';
-import thingsboardApiDatasource from '../api/datasource.service';
+import thingsboardLegend from '../legend.directive';
+import thingsboardTypes from '../../common/types.constant';
+import thingsboardApiDatasource from '../../api/datasource.service';
import WidgetController from './widget.controller';
diff --git a/ui/src/app/components/widget.scss b/ui/src/app/components/widget/widget.scss
similarity index 100%
rename from ui/src/app/components/widget.scss
rename to ui/src/app/components/widget/widget.scss
diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html
index 894c23d482..3920f91aeb 100644
--- a/ui/src/app/dashboard/add-widget.tpl.html
+++ b/ui/src/app/dashboard/add-widget.tpl.html
@@ -34,6 +34,7 @@