diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index af14a3f582..b930cf18fa 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -253,7 +253,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co if (ruleChainConnections && ruleChainConnections.length) { var tasks = []; for (var i = 0; i < ruleChainConnections.length; i++) { - tasks.push(getRuleChain(ruleChainConnections[i].targetRuleChainId.id)); + tasks.push(resolveRuleChain(ruleChainConnections[i].targetRuleChainId.id)); } $q.all(tasks).then( (ruleChains) => { @@ -273,6 +273,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co return deferred.promise; } + function resolveRuleChain(ruleChainId) { + var deferred = $q.defer(); + getRuleChain(ruleChainId, {ignoreErrors: true}).then( + (ruleChain) => { + deferred.resolve(ruleChain); + }, + () => { + deferred.resolve({ + id: {id: ruleChainId, entityType: types.entityType.rulechain} + }); + } + ); + return deferred.promise; + } + function loadRuleNodeComponents() { return componentDescriptorService.getComponentDescriptorsByTypes(types.ruleNodeTypeComponentTypes); } diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 19c249f869..88c6353401 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -281,39 +281,63 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, function exportRuleChain(ruleChainId) { ruleChainService.getRuleChain(ruleChainId).then( - function success(ruleChain) { - var name = ruleChain.name; - name = name.toLowerCase().replace(/\W/g,"_"); - exportToPc(prepareExport(ruleChain), name + '.json'); - //TODO: metadata + (ruleChain) => { + ruleChainService.getRuleChainMetaData(ruleChainId).then( + (ruleChainMetaData) => { + var ruleChainExport = { + ruleChain: prepareRuleChain(ruleChain), + metadata: prepareRuleChainMetaData(ruleChainMetaData) + }; + var name = ruleChain.name; + name = name.toLowerCase().replace(/\W/g,"_"); + exportToPc(ruleChainExport, name + '.json'); + }, + (rejection) => { + processExportRuleChainRejection(rejection); + } + ); }, - function fail(rejection) { - var message = rejection; - if (!message) { - message = $translate.instant('error.unknown-error'); - } - toast.showError($translate.instant('rulechain.export-failed-error', {error: message})); + (rejection) => { + processExportRuleChainRejection(rejection); } ); } + function prepareRuleChain(ruleChain) { + ruleChain = prepareExport(ruleChain); + if (ruleChain.firstRuleNodeId) { + ruleChain.firstRuleNodeId = null; + } + ruleChain.root = false; + return ruleChain; + } + + function prepareRuleChainMetaData(ruleChainMetaData) { + delete ruleChainMetaData.ruleChainId; + for (var i=0;i { + vm.isConfirmOnExit = val; + }); + + vm.errorTooltips = {}; + vm.isFullscreen = false; vm.editingRuleNode = null; @@ -151,6 +167,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $scope.$broadcast('form-submit'); if (theForm.$valid) { theForm.$setPristine(); + if (vm.editingRuleNode.error) { + delete vm.editingRuleNode.error; + } vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; vm.editingRuleNode = angular.copy(vm.editingRuleNode); updateRuleNodesHighlight(); @@ -210,7 +229,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } var instances = angular.element.tooltipster.instances(); instances.forEach((instance) => { - instance.destroy(); + if (!instance.isErrorTooltip) { + instance.destroy(); + } }); } @@ -256,6 +277,71 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, }, 500); } + function updateNodeErrorTooltip(node) { + if (node.error) { + var element = angular.element('#' + node.id); + var tooltip = vm.errorTooltips[node.id]; + if (!tooltip || !element.hasClass("tooltipstered")) { + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 0, + animationDuration: 0, + trigger: 'custom', + triggerOpen: { + click: false, + tap: false + }, + triggerClose: { + click: false, + tap: false, + scroll: false + }, + side: 'top', + trackOrigin: true + } + ); + var content = '
' + + '
' + + '
' + node.error + '
' + + '
' + + '
'; + var contentElement = angular.element(content); + $compile(contentElement)($scope); + tooltip = element.tooltipster('instance'); + tooltip.isErrorTooltip = true; + tooltip.content(contentElement); + vm.errorTooltips[node.id] = tooltip; + } + $mdUtil.nextTick(() => { + tooltip.open(); + }); + } else { + if (vm.errorTooltips[node.id]) { + tooltip = vm.errorTooltips[node.id]; + tooltip.destroy(); + delete vm.errorTooltips[node.id]; + } + } + } + + function updateErrorTooltips(hide) { + for (var nodeId in vm.errorTooltips) { + var tooltip = vm.errorTooltips[nodeId]; + if (hide) { + tooltip.close(); + } else { + tooltip.open(); + } + } + } + + $scope.$watch(function() { + return vm.isEditingRuleNode || vm.isEditingRuleNodeLink; + }, (val) => { + updateErrorTooltips(val); + }); + vm.editCallbacks = { edgeDoubleClick: function (event, edge) { var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source); @@ -381,6 +467,16 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize(true); } } + for (componentType in vm.ruleNodeTypesModel) { + var panel = vm.$mdExpansionPanel(componentType); + if (panel) { + if (!vm.ruleNodeTypesModel[componentType].model.nodes.length) { + panel.collapse(); + } else { + panel.expand(); + } + } + } }); } @@ -512,11 +608,9 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, ruleChainNode = { id: 'rule-chain-node-' + vm.nextNodeID++, additionalInfo: ruleChainConnection.additionalInfo, - targetRuleChainId: ruleChainConnection.targetRuleChainId.id, x: ruleChainConnection.additionalInfo.layoutX, y: ruleChainConnection.additionalInfo.layoutY, component: types.ruleChainNodeComponent, - name: ruleChain.name, nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass, icon: vm.types.ruleNodeType.RULE_CHAIN.icon, connectors: [ @@ -526,6 +620,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } ] }; + if (ruleChain.name) { + ruleChainNode.name = ruleChain.name; + ruleChainNode.targetRuleChainId = ruleChainConnection.targetRuleChainId.id; + } else { + ruleChainNode.name = "Unresolved"; + ruleChainNode.targetRuleChainId = null; + ruleChainNode.error = $translate.instant('rulenode.invalid-target-rulechain'); + } ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; vm.ruleChainModel.nodes.push(ruleChainNode); } @@ -553,11 +655,16 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, updateRuleNodesHighlight(); + validate(); + $mdUtil.nextTick(() => { vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel', function (newVal, oldVal) { - if (!vm.isDirty && !angular.equals(newVal, oldVal)) { - vm.isDirty = true; + if (!angular.equals(newVal, oldVal)) { + validate(); + if (!vm.isDirty) { + vm.isDirty = true; + } } }, true ); @@ -565,91 +672,122 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, } function updateRuleNodesHighlight() { - for (var i=0;i { + vm.isInvalid = false; + for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) { + if (vm.ruleChainModel.nodes[i].error) { + vm.isInvalid = true; + } + updateNodeErrorTooltip(vm.ruleChainModel.nodes[i]); + } + }); + } + function saveRuleChain() { - var ruleChainMetaData = { - ruleChainId: vm.ruleChain.id, - nodes: [], - connections: [], - ruleChainConnections: [] - }; + var saveRuleChainPromise; + if (vm.isImport) { + saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain); + } else { + saveRuleChainPromise = $q.when(vm.ruleChain); + } + saveRuleChainPromise.then( + (ruleChain) => { + vm.ruleChain = ruleChain; + var ruleChainMetaData = { + ruleChainId: vm.ruleChain.id, + nodes: [], + connections: [], + ruleChainConnections: [] + }; - var nodes = []; + var nodes = []; - for (var i=0;i { - vm.ruleChainMetaData = ruleChainMetaData; - prepareRuleChain(); + ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then( + (ruleChainMetaData) => { + vm.ruleChainMetaData = ruleChainMetaData; + if (vm.isImport) { + vm.isDirty = false; + vm.isImport = false; + $mdUtil.nextTick(() => { + $state.go('home.ruleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id}); + }); + } else { + prepareRuleChain(); + } + } + ); } ); } @@ -662,12 +800,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); + var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null; + $mdDialog.show({ controller: 'AddRuleNodeController', controllerAs: 'vm', templateUrl: addRuleNodeTemplate, parent: angular.element($document[0].body), - locals: {ruleNode: ruleNode, ruleChainId: vm.ruleChain.id.id}, + locals: {ruleNode: ruleNode, ruleChainId: ruleChainId}, fullscreen: true, targetEvent: $event }).then(function (ruleNode) { diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index 48559d931a..2aefd82379 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -76,11 +76,52 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider } }, data: { + import: false, searchEnabled: true, pageTitle: 'rulechain.rulechain' }, ncyBreadcrumb: { label: '{"icon": "settings_ethernet", "label": "{{ vm.ruleChain.name }}", "translate": "false"}' } + }).state('home.ruleChains.importRuleChain', { + url: '/ruleChain/import', + reloadOnSearch: false, + module: 'private', + auth: ['SYS_ADMIN', 'TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: ruleChainTemplate, + controller: 'RuleChainController', + controllerAs: 'vm' + } + }, + params: { + ruleChainImport: {} + }, + resolve: { + ruleChain: + /*@ngInject*/ + function($stateParams) { + return $stateParams.ruleChainImport.ruleChain; + }, + ruleChainMetaData: + /*@ngInject*/ + function($stateParams) { + return $stateParams.ruleChainImport.metadata; + }, + ruleNodeComponents: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleNodeComponents(); + } + }, + data: { + import: true, + searchEnabled: true, + pageTitle: 'rulechain.rulechain' + }, + ncyBreadcrumb: { + label: '{"icon": "settings_ethernet", "label": "{{ (\'rulechain.import\' | translate) + \': \'+ vm.ruleChain.name }}", "translate": "false"}' + } }); } diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 850de3c88e..187a722e0f 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -125,13 +125,16 @@ color: #333; border: solid 1px #777; font-size: 12px; - &.tb-rule-node-highlighted { + &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) { box-shadow: 0 0 10px 6px #51cbee; .tb-node-title { text-decoration: underline; font-weight: bold; } } + &.tb-rule-node-invalid { + box-shadow: 0 0 10px 6px #ff5c50; + } &.tb-input-type { background-color: #a3eaa9; user-select: none; @@ -191,10 +194,6 @@ bottom: 0; background-color: #000; opacity: 0; -/* &.tb-rule-node-highlighted { - background-color: green; - opacity: 0.15; - }*/ } &.fc-hover { .fc-node-overlay { @@ -391,6 +390,14 @@ font-size: 14px; width: 300px; color: #333; +} + +.tb-rule-node-error-tooltip { + font-size: 16px; + color: #ea0d0d; +} + +.tb-rule-node-tooltip, .tb-rule-node-error-tooltip { #tooltip-content { .tb-node-title { font-weight: 600; diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index a843b03ffe..37fcd9bfb3 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -16,7 +16,7 @@ --> - @@ -185,7 +185,7 @@ - diff --git a/ui/src/app/rulechain/rulechains.controller.js b/ui/src/app/rulechain/rulechains.controller.js index 2a30b0c98f..7c857f23b6 100644 --- a/ui/src/app/rulechain/rulechains.controller.js +++ b/ui/src/app/rulechain/rulechains.controller.js @@ -63,8 +63,8 @@ export default function RuleChainsController(ruleChainService, userService, impo { onAction: function ($event) { importExport.importRuleChain($event).then( - function() { - vm.grid.refreshList(); + function(ruleChainImport) { + $state.go('home.ruleChains.importRuleChain', {ruleChainImport:ruleChainImport}); } ); }, diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html index 83ce988684..973ea1fa9e 100644 --- a/ui/src/app/rulechain/rulenode.tpl.html +++ b/ui/src/app/rulechain/rulenode.tpl.html @@ -22,8 +22,8 @@ ng-mousedown="callbacks.mouseDown($event, node)" ng-mouseenter="callbacks.mouseEnter($event, node)" ng-mouseleave="callbacks.mouseLeave($event, node)"> -
-
+
+
{{node.icon}}