diff --git a/awx/ui/client/features/templates/templates.strings.js b/awx/ui/client/features/templates/templates.strings.js index e0007f4431..48c9173f7b 100644 --- a/awx/ui/client/features/templates/templates.strings.js +++ b/awx/ui/client/features/templates/templates.strings.js @@ -110,6 +110,8 @@ function TemplatesStrings (BaseString) { JOBS: t.s('JOBS'), PLEASE_CLICK_THE_START_BUTTON: t.s('Please click the start button to build your workflow.'), PLEASE_HOVER_OVER_A_TEMPLATE: t.s('Please hover over a template for additional options.'), + EDIT_LINK_TOOLTIP: t.s('Click to edit link'), + VIEW_LINK_TOOLTIP: t.s('Click to view link'), RUN: t.s('RUN'), CHECK: t.s('CHECK'), SELECT: t.s('SELECT'), @@ -118,6 +120,7 @@ function TemplatesStrings (BaseString) { DETAILS: t.s('DETAILS'), TITLE: t.s('WORKFLOW VISUALIZER'), EDIT_LINK: ({ parentName, childName }) => t.s('EDIT LINK | {{parentName}} to {{childName}}', { parentName, childName }), + VIEW_LINK: ({ parentName, childName }) => t.s('VIEW LINK | {{parentName}} to {{childName}}', { parentName, childName }) } } diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less index f5e9bd7378..b2deec2bf3 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less @@ -1,43 +1,88 @@ -.link circle, -.link polygon, -.link .linkCross, -.node circle, -.node .linkIcon, -.node .WorkflowChart-hoverPath { +.WorkflowChart-node { + font-size: 12px; + font-family: 'Open Sans', sans-serif, 'FontAwesome'; +} + +.WorkflowChart-link { + fill: none; + stroke-width: 2px; +} + +.WorkflowChart-linkOverlay { + fill: @default-interface-txt; +} + +.WorkflowChart-link--active.WorkflowChart-linkOverlay, +.WorkflowChart-linkHovering .WorkflowChart-linkOverlay { + cursor: pointer; + opacity: 1; + fill: @cgrey; +} + +.WorkflowChart-linkHovering .WorkflowChart-linkPath { + cursor: pointer; +} + +.WorkflowChart-link circle, +.WorkflowChart-link polygon, +.WorkflowChart-link .WorkflowChart-betweenNodesIcon, +.WorkflowChart-node .WorkflowChart-nodeAddCircle, +.WorkflowChart-node .WorkflowChart-nodeRemoveCircle, +.WorkflowChart-node .WorkflowChart-nodeAddIcon, +.WorkflowChart-node .WorkflowChart-nodeRemoveIcon { opacity: 0; } -.node .addCircle, .link .addCircle { +.WorkflowChart-node .WorkflowChart-addCircle, .WorkflowChart-link .WorkflowChart-addCircle { fill: @default-succ; } -.addCircle.addHovering { +.WorkflowChart-addCircle.WorkflowChart-addHovering { fill: @default-succ-hov; } -.node .removeCircle { +.WorkflowChart-node .WorkflowChart-nodeRemoveCircle { fill: @default-err; } -.removeCircle.removeHovering { +.WorkflowChart-nodeRemoveCircle.removeHovering { fill: @default-err-hov; } -.node .linkCircle { - fill: @default-link; +.WorkflowChart-node .WorkflowChart-rect { + fill: @default-secondary-bg; } -.node .linkIcon { - color: @default-bg; +.WorkflowChart-rect.WorkflowChart-placeholder { + stroke-dasharray: 3; } -.linkCircle.removeHovering { - fill: @default-link-hov; +.WorkflowChart-node .WorkflowChart-transparentRect { + fill: @default-bg; + opacity: 0; } -.node { - font-size: 12px; - font-family: 'Open Sans', sans-serif, 'FontAwesome'; +.WorkflowChart-alwaysShowAdd circle, +.WorkflowChart-alwaysShowAdd path, +.WorkflowChart-alwaysShowAdd .WorkflowChart-betweenNodesIcon, +.WorkflowChart-nodeHovering .WorkflowChart-nodeAddCircle, +.WorkflowChart-nodeHovering .WorkflowChart-nodeAddIcon, +.WorkflowChart-nodeHovering .WorkflowChart-nodeRemoveCircle, +.WorkflowChart-nodeHovering .WorkflowChart-nodeRemoveIcon, +.WorkflowChart-addHovering circle, +.WorkflowChart-addHovering path, +.WorkflowChart-addHovering .WorkflowChart-betweenNodesIcon { + cursor: pointer; + opacity: 1; +} + +.WorkflowChart-link.WorkflowChart-placeholder { + stroke-dasharray: 3; +} + +.WorkflowChart-svg { + border-bottom-left-radius: 5px; + width: 100%; } .WorkflowChart-defaultText { @@ -49,76 +94,36 @@ cursor: default; } -.node .rect { - fill: @default-secondary-bg; -} - -.rect.placeholder { - stroke-dasharray: 3; -} - -.node .transparentRect { - fill: @default-bg; - opacity: 0; -} - -.WorkflowChart-alwaysShowAdd circle, -.WorkflowChart-alwaysShowAdd path, -.WorkflowChart-alwaysShowAdd .linkCross, -.hovering .addCircle, -.hovering .removeCircle, -.addHovering .betweenNodesCircle, -.hovering .linkCircle, -.hovering .linkIcon, -.hovering .WorkflowChart-hoverPath, -.addHovering .linkCross { - cursor: pointer; - opacity: 1; -} - -.link { - fill: none; - stroke-width: 2px; -} - -.link.placeholder { - stroke-dasharray: 3; -} - -.WorkflowChart-svg { - border-bottom-left-radius: 5px; - width: 100%; -} - -.WorkflowResults-rightSide .WorkflowChart-svg { - background-color: @f6grey; - border: 1px solid @d7grey; - border-top: 0px; - border-bottom-right-radius: 5px; -} .WorkflowChart-nodeTypeCircle { fill: @default-icon; } + .WorkflowChart-nodeTypeLetter { fill: @default-bg; } -.workflowChart-nodeStatus--running { + +.WorkflowChart-nodeStatus--running { fill: @default-icon; } -.workflowChart-nodeStatus--success { + +.WorkflowChart-nodeStatus--success { fill: @default-succ; } -.workflowChart-nodeStatus--failed, .workflowChart-nodeStatus--canceled { + +.WorkflowChart-nodeStatus--failed, .WorkflowChart-nodeStatus--canceled { fill: @default-err; } + .WorkflowChart-detailsLink { fill: @default-link; cursor: pointer; font-size: 10px; } + .WorkflowChart-incompleteIcon { color: @default-warning; } + .WorkflowChart-deletedText { width: 90px; color: @default-interface-txt; @@ -126,6 +131,7 @@ .WorkflowChart-activeNode { fill: @default-link; } + .WorkflowChart-elapsedHolder { background-color: @b7grey; color: @default-bg; @@ -134,40 +140,47 @@ padding: 1px 3px; border-radius: 4px; } + .WorkflowChart-nameText { font-size: 10px; } +.WorkflowChart-tooltip { + pointer-events: none; + text-align: center; +} + .WorkflowChart-tooltipContents { padding: 10px; - background-color: #707070; - color: #FFFFFF; + background-color: @default-interface-txt; + color: @default-bg; border-radius: 4px; word-wrap: break-word; max-width: 325px; + font-size: 10px; } -.WorkflowChart-tooltipArrow { + +.WorkflowChart-tooltipArrow--down { width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; - border-top: 10px solid #707070; + border-top: 10px solid @default-interface-txt; margin: auto; } + +.WorkflowChart-tooltipArrow--right { + width: 0; + height: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-left: 10px solid @default-interface-txt; + margin: auto; + position: relative; + right: -55px; + top: -34px; +} + .WorkflowChart-dashedNode { stroke-dasharray: 5,5; } - -.linkOverlay { - fill: @default-interface-txt; -} - -.linkActiveEdit.linkOverlay, -.overlayHovering .linkOverlay { - cursor: pointer; - opacity: 0.4; -} - -.overlayHovering .linkPath { - cursor: pointer; -} diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index ea5c34a794..07ab752efe 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -239,14 +239,14 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge let nodes = tree.nodes(scope.treeData), links = tree.links(nodes); - let node = svgGroup.selectAll("g.node") + let node = svgGroup.selectAll("g.WorkflowChart-node") .data(nodes, function(d) { d.y = d.depth * 240; return d.id || (d.id = ++i); }); let nodeEnter = node.enter().append("g") - .attr("class", "node") + .attr("class", "WorkflowChart-node") .attr("id", function(d){return "node-" + d.id;}) .attr("parent", function(d){return d.parent ? d.parent.id : null;}) .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); @@ -308,7 +308,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge }) .attr('stroke-width', "2px") .attr("class", function(d) { - let classString = d.placeholder ? "rect placeholder" : "rect"; + let classString = d.placeholder ? "WorkflowChart-rect WorkflowChart-placeholder" : "WorkflowChart-rect"; classString += !d.unifiedJobTemplate ? " WorkflowChart-dashedNode" : ""; return classString; }); @@ -398,7 +398,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge thisNode.append("rect") .attr("width", nodeW) .attr("height", nodeH) - .attr("class", "transparentRect") + .attr("class", "WorkflowChart-transparentRect") .call(edit_node) .on("mouseover", function(d) { if(!d.isStartNode) { @@ -409,13 +409,13 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge // As such, we need to move the nodes after the links so that when the tooltip renders it shows up on top // of the links and not underneath them. I tried rendering the links before the nodes but that lead to // some weird link animation that I didn't care to try to fix. - svgGroup.selectAll("g.node").each(function() { + svgGroup.selectAll("g.WorkflowChart-node").each(function() { this.parentNode.appendChild(this); }); // After the nodes have been properly placed after the links, we need to make sure that the node that // the user is hovering over is at the very end of the list. This way the tooltip will appear on top // of all other nodes. - svgGroup.selectAll("g.node").sort(function (a) { + svgGroup.selectAll("g.WorkflowChart-node").sort(function (a) { return (a.id !== d.id) ? -1 : 1; }); // Render the tooltip quickly in the dom and then remove. This lets us know how big the tooltip is so that we can place @@ -437,14 +437,14 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge }); } d3.select("#node-" + d.id) - .classed("hovering", true); + .classed("WorkflowChart-nodeHovering", true); } }) .on("mouseout", function(d){ $('.WorkflowChart-tooltip').remove(); if(!d.isStartNode) { d3.select("#node-" + d.id) - .classed("hovering", false); + .classed("WorkflowChart-nodeHovering", false); } }); thisNode.append("text") @@ -461,23 +461,23 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge .attr("id", function(d){return "node-" + d.id + "-add";}) .attr("cx", nodeW) .attr("r", 10) - .attr("class", "addCircle nodeCircle") + .attr("class", "WorkflowChart-addCircle WorkflowChart-nodeAddCircle") .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; }) .call(add_node) .on("mouseover", function(d) { d3.select("#node-" + d.id) - .classed("hovering", true); + .classed("WorkflowChart-nodeHovering", true); d3.select("#node-" + d.id + "-add") - .classed("addHovering", true); + .classed("WorkflowChart-addHovering", true); }) .on("mouseout", function(d){ d3.select("#node-" + d.id) - .classed("hovering", false); + .classed("WorkflowChart-nodeHovering", false); d3.select("#node-" + d.id + "-add") - .classed("addHovering", false); + .classed("WorkflowChart-addHovering", false); }); thisNode.append("path") - .attr("class", "nodeAddCross WorkflowChart-hoverPath") + .attr("class", "WorkflowChart-nodeAddIcon") .style("fill", "white") .attr("transform", function() { return "translate(" + nodeW + "," + 0 + ")"; }) .attr("d", d3.svg.symbol() @@ -488,38 +488,38 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge .call(add_node) .on("mouseover", function(d) { d3.select("#node-" + d.id) - .classed("hovering", true); + .classed("WorkflowChart-nodeHovering", true); d3.select("#node-" + d.id + "-add") - .classed("addHovering", true); + .classed("WorkflowChart-addHovering", true); }) .on("mouseout", function(d){ d3.select("#node-" + d.id) - .classed("hovering", false); + .classed("WorkflowChart-nodeHovering", false); d3.select("#node-" + d.id + "-add") - .classed("addHovering", false); + .classed("WorkflowChart-addHovering", false); }); thisNode.append("circle") .attr("id", function(d){return "node-" + d.id + "-remove";}) .attr("cx", nodeW) .attr("cy", nodeH) .attr("r", 10) - .attr("class", "removeCircle") + .attr("class", "WorkflowChart-nodeRemoveCircle") .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; }) .call(remove_node) .on("mouseover", function(d) { d3.select("#node-" + d.id) - .classed("hovering", true); + .classed("WorkflowChart-nodeHovering", true); d3.select("#node-" + d.id + "-remove") .classed("removeHovering", true); }) .on("mouseout", function(d){ d3.select("#node-" + d.id) - .classed("hovering", false); + .classed("WorkflowChart-nodeHovering", false); d3.select("#node-" + d.id + "-remove") .classed("removeHovering", false); }); thisNode.append("path") - .attr("class", "nodeRemoveCross WorkflowChart-hoverPath") + .attr("class", "WorkflowChart-nodeRemoveIcon") .style("fill", "white") .attr("transform", function() { return "translate(" + nodeW + "," + nodeH + ") rotate(-45)"; }) .attr("d", d3.svg.symbol() @@ -530,60 +530,16 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge .call(remove_node) .on("mouseover", function(d) { d3.select("#node-" + d.id) - .classed("hovering", true); + .classed("WorkflowChart-nodeHovering", true); d3.select("#node-" + d.id + "-remove") .classed("removeHovering", true); }) .on("mouseout", function(d){ d3.select("#node-" + d.id) - .classed("hovering", false); + .classed("WorkflowChart-nodeHovering", false); d3.select("#node-" + d.id + "-remove") .classed("removeHovering", false); }); - // thisNode.append("circle") - // .attr("id", function(d){return "node-" + d.id + "-link";}) - // .attr("cx", nodeW) - // .attr("cy", nodeH/2) - // .attr("r", 10) - // .attr("class", "linkCircle nodeCircle") - // .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; }) - // .call(link_node) - // .on("mouseover", function(d) { - // d3.select("#node-" + d.id) - // .classed("hovering", true); - // d3.select("#node-" + d.id + "-link") - // .classed("addHovering", true); - // }) - // .on("mouseout", function(d){ - // d3.select("#node-" + d.id) - // .classed("hovering", false); - // d3.select("#node-" + d.id + "-link") - // .classed("addHovering", false); - // }); - // // TODO: clean up the placement of this icon... this works but it's not - // // clean - // thisNode.append("foreignObject") - // .attr("x", nodeW - 6) - // .attr("y", nodeH/2 - 9) - // .style("font-size","14px") - // .html(function () { - // return ``; - // }) - // .attr("class", "linkIcon") - // .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; }) - // .call(link_node) - // .on("mouseover", function(d) { - // d3.select("#node-" + d.id) - // .classed("hovering", true); - // d3.select("#node-" + d.id + "-link") - // .classed("addHovering", true); - // }) - // .on("mouseout", function(d){ - // d3.select("#node-" + d.id) - // .classed("hovering", false); - // d3.select("#node-" + d.id + "-link") - // .classed("addHovering", false); - // }); thisNode.append("circle") .attr("class", function(d) { @@ -593,25 +549,25 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge if(d.job){ switch(d.job.status) { case "pending": - statusClass += "workflowChart-nodeStatus--running"; + statusClass += "WorkflowChart-nodeStatus--running"; break; case "waiting": - statusClass += "workflowChart-nodeStatus--running"; + statusClass += "WorkflowChart-nodeStatus--running"; break; case "running": - statusClass += "workflowChart-nodeStatus--running"; + statusClass += "WorkflowChart-nodeStatus--running"; break; case "successful": - statusClass += "workflowChart-nodeStatus--success"; + statusClass += "WorkflowChart-nodeStatus--success"; break; case "failed": - statusClass += "workflowChart-nodeStatus--failed"; + statusClass += "WorkflowChart-nodeStatus--failed"; break; case "error": - statusClass += "workflowChart-nodeStatus--failed"; + statusClass += "WorkflowChart-nodeStatus--failed"; break; case "canceled": - statusClass += "workflowChart-nodeStatus--canceled"; + statusClass += "WorkflowChart-nodeStatus--canceled"; break; } } @@ -652,63 +608,149 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge graphLoaded = true; - let link = svgGroup.selectAll("g.link") + let link = svgGroup.selectAll("g.WorkflowChart-link") .data(links, function(d) { return d.source.id + "-" + d.target.id; }); let linkEnter = link.enter().append("g") - .attr("class", "link") + .attr("class", "WorkflowChart-link") .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;}); linkEnter.append("polygon", "g") .attr("class", function(d) { - let linkClasses = ["linkOverlay"]; + let linkClasses = ["WorkflowChart-linkOverlay"]; if (d.source.isLinkEditParent && d.target.isLinkEditChild) { - linkClasses.push("linkActiveEdit"); + linkClasses.push("WorkflowChart-link--active"); } return linkClasses.join(' '); }) .attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-overlay";}) .attr("points",function(d) { - const pt1 = [d.source.y + nodeW, d.source.x + 10 + nodeH/2].join(","); - const pt2 = [d.target.y,d.target.x + 10 + nodeH/2].join(","); - const pt3 = [d.target.y,d.target.x - 10 + nodeH/2].join(","); - const pt4 = [d.source.y + nodeW,d.source.x - 10 + nodeH/2].join(","); + let x1 = d.source.y + nodeW; + let y1 = d.source.x + nodeH / 2; + let x2 = d.target.y; + let y2 = d.target.x + nodeH / 2; + let slope = (y2 - y1)/(x2-x1); + let yIntercept = y1 - slope*x1; + let orthogonalDistance = 8; + + const pt1 = [x1, slope*x1 + yIntercept + orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + const pt2 = [x2, slope*x2 + yIntercept + orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + const pt3 = [x2, slope*x2 + yIntercept - orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + const pt4 = [x1, slope*x1 + yIntercept - orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + return [pt1, pt2, pt3, pt4].join(" "); }) .call(edit_link) .on("mouseover", function(d) { - if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') { + if(!d.source.isStartNode && !d.source.placeholder && !d.target.placeholder && scope.mode !== 'details') { d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("overlayHovering", true); + .classed("WorkflowChart-linkHovering", true); + + let xPos, yPos, arrowClass; + if (d.source.x === d.target.x) { + xPos = d.source.y + nodeW + ((d.target.y - (d.source.y + nodeW))/2) - (100/2); + yPos = (d.source.x + nodeH/2 - d.target.x + nodeH/2)/2 + (d.target.x + nodeH/2) - 100; + arrowClass = 'WorkflowChart-tooltipArrow--down'; + } else { + xPos = d.source.y + nodeW + ((d.target.y - (d.source.y + nodeW))/2) - 115; + yPos = (d.source.x + nodeH/2 - d.target.x + nodeH/2)/2 + (d.target.x + nodeH/2) - 50; + arrowClass = 'WorkflowChart-tooltipArrow--right'; + } + + let edgeTypeLabel; + + switch(d.target.edgeType) { + case "always": + edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS'); + break; + case "success": + edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS'); + break; + case "failure": + edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE'); + break; + } + + let linkInstructionText = _.get(scope, 'workflowJobTemplateObj.summary_fields.user_capabilities.edit') ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP'); + + linkEnter.append("foreignObject") + .attr("x", xPos) + .attr("y", yPos) + .attr("width", 100) + .attr("height", 60) + .attr("class", "WorkflowChart-tooltip") + .html(function(){ + return `
${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}
${linkInstructionText}
`; + }); } + }) .on("mouseout", function(d){ if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') { d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("overlayHovering", false); + .classed("WorkflowChart-linkHovering", false); } + $('.WorkflowChart-tooltip').remove(); }); // Add entering links in the parent’s old position. linkEnter.append("path", "g") .attr("class", function(d) { - return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath"; + return (d.source.placeholder || d.target.placeholder) ? "WorkflowChart-linkPath WorkflowChart-placeholder" : "WorkflowChart-linkPath"; }) .attr("d", lineData) .call(edit_link) - .on("mouseover", function(d) { - if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') { + .on("mouseenter", function(d) { + if(!d.source.isStartNode && !d.source.placeholder && !d.target.placeholder && scope.mode !== 'details') { d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("overlayHovering", true); + .classed("WorkflowChart-linkHovering", true); + + let xPos, yPos, arrowClass; + if (d.source.x === d.target.x) { + xPos = d.source.y + nodeW + ((d.target.y - (d.source.y + nodeW))/2) - (100/2); + yPos = (d.source.x + nodeH/2 - d.target.x + nodeH/2)/2 + (d.target.x + nodeH/2) - 100; + arrowClass = 'WorkflowChart-tooltipArrow--down'; + } else { + xPos = d.source.y + nodeW + ((d.target.y - (d.source.y + nodeW))/2) - 115; + yPos = (d.source.x + nodeH/2 - d.target.x + nodeH/2)/2 + (d.target.x + nodeH/2) - 50; + arrowClass = 'WorkflowChart-tooltipArrow--right'; + } + + let edgeTypeLabel; + + switch(d.target.edgeType) { + case "always": + edgeTypeLabel = TemplatesStrings.get('workflow_maker.ALWAYS'); + break; + case "success": + edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_SUCCESS'); + break; + case "failure": + edgeTypeLabel = TemplatesStrings.get('workflow_maker.ON_FAILURE'); + break; + } + + let linkInstructionText = _.get(scope, 'workflowJobTemplateObj.summary_fields.user_capabilities.edit') ? TemplatesStrings.get('workflow_maker.EDIT_LINK_TOOLTIP') : TemplatesStrings.get('workflow_maker.VIEW_LINK_TOOLTIP'); + + linkEnter.append("foreignObject") + .attr("x", xPos) + .attr("y", yPos) + .attr("width", 100) + .attr("height", 60) + .attr("class", "WorkflowChart-tooltip") + .html(function(){ + return `
${TemplatesStrings.get('workflow_maker.RUN')}: ${edgeTypeLabel}
${linkInstructionText}
`; + }); } }) - .on("mouseout", function(d){ + .on("mouseleave", function(d){ if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') { d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("overlayHovering", false); + .classed("WorkflowChart-linkHovering", false); } + $('.WorkflowChart-tooltip').remove(); }) .attr('stroke', function(d) { if(d.target.edgeType) { @@ -736,20 +778,20 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2; }) .attr("r", 10) - .attr("class", "addCircle betweenNodesCircle") + .attr("class", "WorkflowChart-addCircle WorkflowChart-circleBetweenNodes") .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; }) .call(add_node_between) .on("mouseover", function(d) { d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("addHovering", true); + .classed("WorkflowChart-addHovering", true); }) .on("mouseout", function(d){ d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("addHovering", false); + .classed("WorkflowChart-addHovering", false); }); linkEnter.append("path") - .attr("class", "linkCross") + .attr("class", "WorkflowChart-betweenNodesIcon") .style("fill", "white") .attr("transform", function(d) { let translate; @@ -769,11 +811,11 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge .call(add_node_between) .on("mouseover", function(d) { d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("addHovering", true); + .classed("WorkflowChart-addHovering", true); }) .on("mouseout", function(d){ d3.select("#link-" + d.source.id + "-" + d.target.id) - .classed("addHovering", false); + .classed("WorkflowChart-addHovering", false); }); link.exit().remove(); @@ -781,21 +823,21 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge // Transition nodes and links to their new positions. let t = baseSvg.transition(); - t.selectAll(".nodeCircle") + t.selectAll(".WorkflowChart-nodeAddCircle") .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; }); - t.selectAll(".nodeAddCross") + t.selectAll(".WorkflowChart-nodeAddIcon") .style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; }); - t.selectAll(".removeCircle") + t.selectAll(".WorkflowChart-nodeRemoveCircle") .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; }); - t.selectAll(".nodeRemoveCross") + t.selectAll(".WorkflowChart-nodeRemoveIcon") .style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; }); - t.selectAll(".linkPath") + t.selectAll(".WorkflowChart-linkPath") .attr("class", function(d) { - return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath"; + return (d.source.placeholder || d.target.placeholder) ? "WorkflowChart-linkPath WorkflowChart-placeholder" : "WorkflowChart-linkPath"; }) .attr("d", lineData) .attr('stroke', function(d) { @@ -815,7 +857,8 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge } }); - t.selectAll(".betweenNodesCircle") + t.selectAll(".WorkflowChart-circleBetweenNodes") + .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; }) .attr("cx", function(d) { return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2; }) @@ -823,23 +866,32 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge return (d.source.isStartNode) ? ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 : (d.target.x + d.source.x + nodeH) / 2; }); - t.selectAll(".linkOverlay") + t.selectAll(".WorkflowChart-linkOverlay") .attr("class", function(d) { - let linkClasses = ["linkOverlay"]; + let linkClasses = ["WorkflowChart-linkOverlay"]; if (d.source.isLinkEditParent && d.target.isLinkEditChild) { - linkClasses.push("linkActiveEdit"); + linkClasses.push("WorkflowChart-link--active"); } return linkClasses.join(' '); }) .attr("points",function(d) { - const pt1 = [d.source.y + nodeW, d.source.x + 10 + nodeH/2].join(","); - const pt2 = [d.target.y,d.target.x + 10 + nodeH/2].join(","); - const pt3 = [d.target.y,d.target.x - 10 + nodeH/2].join(","); - const pt4 = [d.source.y + nodeW,d.source.x - 10 + nodeH/2].join(","); + let x1 = d.source.y + nodeW; + let y1 = d.source.x + nodeH / 2; + let x2 = d.target.y; + let y2 = d.target.x + nodeH / 2; + let slope = (y2 - y1)/(x2-x1); + let yIntercept = y1 - slope*x1; + let orthogonalDistance = 8; + + const pt1 = [x1, slope*x1 + yIntercept + orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + const pt2 = [x2, slope*x2 + yIntercept + orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + const pt3 = [x2, slope*x2 + yIntercept - orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + const pt4 = [x1, slope*x1 + yIntercept - orthogonalDistance*Math.sqrt(1+slope*slope)].join(","); + return [pt1, pt2, pt3, pt4].join(" "); }); - t.selectAll(".linkCross") + t.selectAll(".WorkflowChart-betweenNodesIcon") .style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; }) .attr("transform", function(d) { let translate; @@ -852,7 +904,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge return translate; }); - t.selectAll(".rect") + t.selectAll(".WorkflowChart-rect") .attr('stroke', function(d) { if(d.job && d.job.status) { if(d.job.status === "successful"){ @@ -870,12 +922,12 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge } }) .attr("class", function(d) { - let classString = d.placeholder ? "rect placeholder" : "rect"; + let classString = d.placeholder ? "WorkflowChart-rect WorkflowChart-placeholder" : "WorkflowChart-rect"; classString += !d.unifiedJobTemplate ? " WorkflowChart-dashedNode" : ""; return classString; }); - t.selectAll(".node") + t.selectAll(".WorkflowChart-node") .attr("parent", function(d){return d.parent ? d.parent.id : null;}) .attr("transform", function(d) {d.px = d.x; d.py = d.y; return "translate(" + d.y + "," + d.x + ")"; }); @@ -937,25 +989,25 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge if(d.job){ switch(d.job.status) { case "pending": - statusClass += "workflowChart-nodeStatus--running"; + statusClass += "WorkflowChart-nodeStatus--running"; break; case "waiting": - statusClass += "workflowChart-nodeStatus--running"; + statusClass += "WorkflowChart-nodeStatus--running"; break; case "running": - statusClass += "workflowChart-nodeStatus--running"; + statusClass += "WorkflowChart-nodeStatus--running"; break; case "successful": - statusClass += "workflowChart-nodeStatus--success"; + statusClass += "WorkflowChart-nodeStatus--success"; break; case "failed": - statusClass += "workflowChart-nodeStatus--failed"; + statusClass += "WorkflowChart-nodeStatus--failed"; break; case "error": - statusClass += "workflowChart-nodeStatus--failed"; + statusClass += "WorkflowChart-nodeStatus--failed"; break; case "canceled": - statusClass += "workflowChart-nodeStatus--canceled"; + statusClass += "WorkflowChart-nodeStatus--canceled"; break; } } @@ -1058,11 +1110,10 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge function edit_link() { this.on("click", function(d) { - if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details'){ - // What if the node is new? it won't have a nodeId right? + if(!d.source.isStartNode && !d.source.placeholder && !d.target.placeholder && scope.mode !== 'details'){ scope.editLink({ - parentId: d.source.nodeId, - childId: d.target.nodeId + parentId: d.source.id, + childId: d.target.id }); } }); diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.directive.js index 00b3afc765..8591b9a728 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.directive.js @@ -11,6 +11,7 @@ export default ['templateUrl', return { scope: { linkConfig: '<', + readOnly: '<', cancel: '&', select: '&' }, diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.partial.html index cf03f1624c..a09dcd0618 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-link-form.partial.html @@ -1,4 +1,4 @@ -
{{:: strings.get('workflow_maker.EDIT_LINK', {parentName: linkConfig.parent.name, childName: linkConfig.child.name}) }}
+
{{readOnly ? strings.get('workflow_maker.VIEW_LINK', {parentName: linkConfig.parent.name, childName: linkConfig.child.name}) : strings.get('workflow_maker.EDIT_LINK', {parentName: linkConfig.parent.name, childName: linkConfig.child.name}) }}
- - + + +
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js index 41e8b11142..2310c067bf 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js @@ -4,8 +4,635 @@ * All Rights Reserved *************************************************/ -export default ['$scope', - function($scope) { - console.log('inside wnf controller'); +export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService', 'Rest', '$q', + 'WorkflowService', 'TemplatesStrings', 'CreateSelect2', 'Empty', 'generateList', 'QuerySet', + 'GetBasePath', 'TemplateList', 'ProjectList', 'InventorySourcesList', + function($scope, TemplatesService, JobTemplate, PromptService, Rest, $q, + WorkflowService, TemplatesStrings, CreateSelect2, Empty, generateList, qs, + GetBasePath, TemplateList, ProjectList, InventorySourcesList + ) { + + let promptWatcher, credentialsWatcher, surveyQuestionWatcher, listPromises = []; + + $scope.strings = TemplatesStrings; + + let templateList = _.cloneDeep(TemplateList); + delete templateList.actions; + delete templateList.fields.type; + delete templateList.fields.description; + delete templateList.fields.smart_status; + delete templateList.fields.labels; + delete templateList.fieldActions; + templateList.fields.name.columnClass = "col-md-8"; + templateList.disableRow = "{{ readOnly }}"; + templateList.disableRowValue = 'readOnly'; + templateList.fields.info = { + ngInclude: "'/static/partials/job-template-details.html'", + type: 'template', + columnClass: 'col-md-3', + label: '', + nosort: true + }; + templateList.maxVisiblePages = 5; + templateList.searchBarFullWidth = true; + $scope.templateList = templateList; + + let inventorySourceList = _.cloneDeep(InventorySourcesList); + inventorySourceList.maxVisiblePages = 5; + inventorySourceList.searchBarFullWidth = true; + inventorySourceList.disableRow = "{{ readOnly }}"; + inventorySourceList.disableRowValue = 'readOnly'; + $scope.inventorySourceList = inventorySourceList; + + let projectList = _.cloneDeep(ProjectList); + delete projectList.fields.status; + delete projectList.fields.scm_type; + delete projectList.fields.last_updated; + projectList.fields.name.columnClass = "col-md-11"; + projectList.maxVisiblePages = 5; + projectList.searchBarFullWidth = true; + projectList.disableRow = "{{ readOnly }}"; + projectList.disableRowValue = 'readOnly'; + $scope.projectList = projectList; + + $scope.$watch('node', (newNode, oldNode) => { + if (oldNode.id !== newNode.id) { + setupNodeForm(); + } + }); + + $scope.$watchGroup(['templates', 'projects', 'inventory_sources', 'activeTab'], () => { + // TODO: make this more concise + switch($scope.activeTab) { + case 'jobs': + $scope.templates.forEach(function(row, i) { + if(_.hasIn($scope, 'node.unifiedJobTemplate.id') && row.id === $scope.node.unifiedJobTemplate.id) { + $scope.templates[i].checked = 1; + } + else { + $scope.templates[i].checked = 0; + } + }); + break; + case 'project_syncs': + $scope.projects.forEach(function(row, i) { + if(_.hasIn($scope, 'node.unifiedJobTemplate.id') && row.id === $scope.node.unifiedJobTemplate.id) { + $scope.projects[i].checked = 1; + } + else { + $scope.projects[i].checked = 0; + } + }); + break; + case 'inventory_syncs': + $scope.inventory_sources.forEach(function(row, i) { + if(_.hasIn($scope, 'node.unifiedJobTemplate.id') && row.id === $scope.node.unifiedJobTemplate.id) { + $scope.inventory_sources[i].checked = 1; + } + else { + $scope.inventory_sources[i].checked = 0; + } + }); + break; + } + }); + + const checkCredentialsForRequiredPasswords = () => { + let credentialRequiresPassword = false; + $scope.promptData.prompts.credentials.value.forEach((credential) => { + if ((credential.passwords_needed && + credential.passwords_needed.length > 0) || + (_.has(credential, 'inputs.vault_password') && + credential.inputs.vault_password === "ASK") + ) { + credentialRequiresPassword = true; + } + }); + $scope.credentialRequiresPassword = credentialRequiresPassword; + }; + + const watchForPromptChanges = () => { + let promptDataToWatch = [ + 'promptData.prompts.inventory.value', + 'promptData.prompts.verbosity.value', + 'missingSurveyValue' + ]; + + promptWatcher = $scope.$watchGroup(promptDataToWatch, function() { + let missingPromptValue = false; + if ($scope.missingSurveyValue) { + missingPromptValue = true; + } else if (!$scope.promptData.prompts.inventory.value || !$scope.promptData.prompts.inventory.value.id) { + missingPromptValue = true; + } + $scope.promptModalMissingReqFields = missingPromptValue; + }); + + if ($scope.promptData.launchConf.ask_credential_on_launch && $scope.credentialRequiresPassword) { + credentialsWatcher = $scope.$watch('promptData.prompts.credentials', () => { + checkCredentialsForRequiredPasswords(); + }); + } + }; + + const finishConfiguringEdit = () => { + + let jobTemplate = new JobTemplate(); + + console.log($scope.node); + + if (!_.isEmpty($scope.node.promptData)) { + $scope.promptData = _.cloneDeep($scope.node.promptData); + const launchConf = $scope.promptData.launchConf; + + if (!launchConf.survey_enabled && + !launchConf.ask_inventory_on_launch && + !launchConf.ask_credential_on_launch && + !launchConf.ask_verbosity_on_launch && + !launchConf.ask_job_type_on_launch && + !launchConf.ask_limit_on_launch && + !launchConf.ask_tags_on_launch && + !launchConf.ask_skip_tags_on_launch && + !launchConf.ask_diff_mode_on_launch && + !launchConf.credential_needed_to_start && + !launchConf.ask_variables_on_launch && + launchConf.variables_needed_to_start.length === 0) { + $scope.showPromptButton = false; + $scope.promptModalMissingReqFields = false; + } else { + $scope.showPromptButton = true; + + if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'node.originalNodeObj.summary_fields.inventory')) { + $scope.promptModalMissingReqFields = true; + } else { + $scope.promptModalMissingReqFields = false; + } + } + $scope.nodeFormDataLoaded = true; + } else if ( + _.get($scope, 'node.unifiedJobTemplate.unified_job_type') === 'job_template' || + _.get($scope, 'node.unifiedJobTemplate.type') === 'job_template' + ) { + let promises = [jobTemplate.optionsLaunch($scope.node.unifiedJobTemplate.id), jobTemplate.getLaunch($scope.node.unifiedJobTemplate.id)]; + + if (_.has($scope, 'node.originalNodeObj.related.credentials')) { + Rest.setUrl($scope.node.originalNodeObj.related.credentials); + promises.push(Rest.get()); + } + + $q.all(promises) + .then((responses) => { + let launchOptions = responses[0].data, + launchConf = responses[1].data, + workflowNodeCredentials = responses[2] ? responses[2].data.results : []; + + let prompts = PromptService.processPromptValues({ + launchConf: responses[1].data, + launchOptions: responses[0].data, + currentValues: $scope.node.originalNodeObj + }); + + let defaultCredsWithoutOverrides = []; + + prompts.credentials.previousOverrides = _.cloneDeep(workflowNodeCredentials); + + const credentialHasScheduleOverride = (templateDefaultCred) => { + let credentialHasOverride = false; + workflowNodeCredentials.forEach((scheduleCred) => { + if (templateDefaultCred.credential_type === scheduleCred.credential_type) { + if ( + (!templateDefaultCred.vault_id && !scheduleCred.inputs.vault_id) || + (templateDefaultCred.vault_id && scheduleCred.inputs.vault_id && templateDefaultCred.vault_id === scheduleCred.inputs.vault_id) + ) { + credentialHasOverride = true; + } + } + }); + + return credentialHasOverride; + }; + + if (_.has(launchConf, 'defaults.credentials')) { + launchConf.defaults.credentials.forEach((defaultCred) => { + if (!credentialHasScheduleOverride(defaultCred)) { + defaultCredsWithoutOverrides.push(defaultCred); + } + }); + } + + prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides); + + if ((!$scope.node.unifiedJobTemplate.inventory && !launchConf.ask_inventory_on_launch) || !$scope.node.unifiedJobTemplate.project) { + $scope.selectedTemplateInvalid = true; + } else { + $scope.selectedTemplateInvalid = false; + } + + let credentialRequiresPassword = false; + + prompts.credentials.value.forEach((credential) => { + if(credential.inputs) { + if ((credential.inputs.password && credential.inputs.password === "ASK") || + (credential.inputs.become_password && credential.inputs.become_password === "ASK") || + (credential.inputs.ssh_key_unlock && credential.inputs.ssh_key_unlock === "ASK") || + (credential.inputs.vault_password && credential.inputs.vault_password === "ASK") + ) { + credentialRequiresPassword = true; + } + } else if (credential.passwords_needed && credential.passwords_needed.length > 0) { + credentialRequiresPassword = true; + } + }); + + $scope.credentialRequiresPassword = credentialRequiresPassword; + + if (!launchConf.survey_enabled && + !launchConf.ask_inventory_on_launch && + !launchConf.ask_credential_on_launch && + !launchConf.ask_verbosity_on_launch && + !launchConf.ask_job_type_on_launch && + !launchConf.ask_limit_on_launch && + !launchConf.ask_tags_on_launch && + !launchConf.ask_skip_tags_on_launch && + !launchConf.ask_diff_mode_on_launch && + !launchConf.credential_needed_to_start && + !launchConf.ask_variables_on_launch && + launchConf.variables_needed_to_start.length === 0) { + $scope.showPromptButton = false; + $scope.promptModalMissingReqFields = false; + $scope.nodeFormDataLoaded = true; + } else { + $scope.showPromptButton = true; + + if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'node.originalNodeObj.summary_fields.inventory')) { + $scope.promptModalMissingReqFields = true; + } else { + $scope.promptModalMissingReqFields = false; + } + + if (responses[1].data.survey_enabled) { + // go out and get the survey questions + jobTemplate.getSurveyQuestions($scope.node.unifiedJobTemplate.id) + .then((surveyQuestionRes) => { + + let processed = PromptService.processSurveyQuestions({ + surveyQuestions: surveyQuestionRes.data.spec, + extra_data: _.cloneDeep($scope.node.originalNodeObj.extra_data) + }); + + $scope.missingSurveyValue = processed.missingSurveyValue; + + $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); + + $scope.node.promptData = $scope.promptData = { + launchConf: launchConf, + launchOptions: launchOptions, + prompts: prompts, + surveyQuestions: surveyQuestionRes.data.spec, + template: $scope.node.unifiedJobTemplate.id + }; + + surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { + let missingSurveyValue = false; + _.each($scope.promptData.surveyQuestions, (question) => { + if (question.required && (Empty(question.model) || question.model === [])) { + missingSurveyValue = true; + } + }); + $scope.missingSurveyValue = missingSurveyValue; + }, true); + + checkCredentialsForRequiredPasswords(); + + watchForPromptChanges(); + + $scope.nodeFormDataLoaded = true; + }); + } else { + $scope.node.promptData = $scope.promptData = { + launchConf: launchConf, + launchOptions: launchOptions, + prompts: prompts, + template: $scope.node.unifiedJobTemplate.id + }; + + checkCredentialsForRequiredPasswords(); + + watchForPromptChanges(); + + $scope.nodeFormDataLoaded = true; + } + } + }); + } else { + $scope.nodeFormDataLoaded = true; + } + + if (_.get($scope, 'node.unifiedJobTemplate')) { + if (_.get($scope, 'node.unifiedJobTemplate.type') === "job_template") { + $scope.activeTab = "jobs"; + } + + $scope.selectedTemplate = $scope.node.unifiedJobTemplate; + + if ($scope.selectedTemplate.unified_job_type) { + switch ($scope.selectedTemplate.unified_job_type) { + case "job": + $scope.activeTab = "jobs"; + break; + case "project_update": + $scope.activeTab = "project_syncs"; + break; + case "inventory_update": + $scope.activeTab = "inventory_syncs"; + break; + } + } else if ($scope.selectedTemplate.type) { + switch ($scope.selectedTemplate.type) { + case "job_template": + $scope.activeTab = "jobs"; + break; + case "project": + $scope.activeTab = "project_syncs"; + break; + case "inventory_source": + $scope.activeTab = "inventory_syncs"; + break; + } + } + } else { + $scope.activeTab = "jobs"; + } + + if ($scope.mode === 'add') { + const alwaysOption = { + label: $scope.strings.get('workflow_maker.ALWAYS'), + value: 'always' + }; + const successOption = { + label: $scope.strings.get('workflow_maker.ON_SUCCESS'), + value: 'success' + }; + const failureOption = { + label: $scope.strings.get('workflow_maker.ON_FAILURE'), + value: 'failure' + }; + $scope.edgeTypeOptions = [alwaysOption]; + switch($scope.node.isRoot) { + case true: + $scope.edgeType = alwaysOption; + break; + case false: + $scope.edgeType = successOption; + $scope.edgeTypeOptions.push(successOption, failureOption); + break; + } + CreateSelect2({ + element: '#workflow_node_edge_3', + multiple: false + }); + + $scope.nodeFormDataLoaded = true; + } + }; + // Determine whether or not we need to go out and GET this nodes unified job template + // in order to determine whether or not prompt fields are needed + + $scope.openPromptModal = function() { + $scope.promptData.triggerModalOpen = true; + }; + + $scope.toggle_row = function(selectedRow) { + if (!$scope.readOnly) { + // TODO: make this more concise + switch($scope.activeTab) { + case 'jobs': + $scope.templates.forEach(function(row, i) { + if (row.id === selectedRow.id) { + $scope.templates[i].checked = 1; + + } else { + $scope.templates[i].checked = 0; + } + }); + break; + case 'project_syncs': + $scope.projects.forEach(function(row, i) { + if (row.id === selectedRow.id) { + $scope.projects[i].checked = 1; + } else { + $scope.projects[i].checked = 0; + } + }); + break; + case 'inventory_syncs': + $scope.inventory_sources.forEach(function(row, i) { + if (row.id === selectedRow.id) { + $scope.inventory_sources[i].checked = 1; + } else { + $scope.inventory_sources[i].checked = 0; + } + }); + break; + } + templateManuallySelected(selectedRow); + } + }; + + const templateManuallySelected = (selectedTemplate) => { + + if (promptWatcher) { + promptWatcher(); + } + + if (surveyQuestionWatcher) { + surveyQuestionWatcher(); + } + + if (credentialsWatcher) { + credentialsWatcher(); + } + + $scope.promptData = null; + + if (selectedTemplate.type === "job_template") { + let jobTemplate = new JobTemplate(); + + $q.all([jobTemplate.optionsLaunch(selectedTemplate.id), jobTemplate.getLaunch(selectedTemplate.id)]) + .then((responses) => { + let launchConf = responses[1].data; + + if ((!selectedTemplate.inventory && !launchConf.ask_inventory_on_launch) || !selectedTemplate.project) { + $scope.selectedTemplateInvalid = true; + } else { + $scope.selectedTemplateInvalid = false; + } + + if (launchConf.passwords_needed_to_start && launchConf.passwords_needed_to_start.length > 0) { + $scope.credentialRequiresPassword = true; + } else { + $scope.credentialRequiresPassword = false; + } + + $scope.selectedTemplate = angular.copy(selectedTemplate); + + if (!launchConf.survey_enabled && + !launchConf.ask_inventory_on_launch && + !launchConf.ask_credential_on_launch && + !launchConf.ask_verbosity_on_launch && + !launchConf.ask_job_type_on_launch && + !launchConf.ask_limit_on_launch && + !launchConf.ask_tags_on_launch && + !launchConf.ask_skip_tags_on_launch && + !launchConf.ask_diff_mode_on_launch && + !launchConf.credential_needed_to_start && + !launchConf.ask_variables_on_launch && + launchConf.variables_needed_to_start.length === 0) { + $scope.showPromptButton = false; + $scope.promptModalMissingReqFields = false; + } else { + $scope.showPromptButton = true; + + if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory')) { + $scope.promptModalMissingReqFields = true; + } else { + $scope.promptModalMissingReqFields = false; + } + + if (launchConf.survey_enabled) { + // go out and get the survey questions + jobTemplate.getSurveyQuestions(selectedTemplate.id) + .then((surveyQuestionRes) => { + + let processed = PromptService.processSurveyQuestions({ + surveyQuestions: surveyQuestionRes.data.spec + }); + + $scope.missingSurveyValue = processed.missingSurveyValue; + + $scope.promptData = { + launchConf: responses[1].data, + launchOptions: responses[0].data, + surveyQuestions: processed.surveyQuestions, + template: selectedTemplate.id, + prompts: PromptService.processPromptValues({ + launchConf: responses[1].data, + launchOptions: responses[0].data + }), + }; + + surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { + let missingSurveyValue = false; + _.each($scope.promptData.surveyQuestions, (question) => { + if (question.required && (Empty(question.model) || question.model === [])) { + missingSurveyValue = true; + } + }); + $scope.missingSurveyValue = missingSurveyValue; + }, true); + + watchForPromptChanges(); + }); + } else { + $scope.promptData = { + launchConf: responses[1].data, + launchOptions: responses[0].data, + template: selectedTemplate.id, + prompts: PromptService.processPromptValues({ + launchConf: responses[1].data, + launchOptions: responses[0].data + }), + }; + + watchForPromptChanges(); + } + } + }); + } else { + $scope.selectedTemplate = angular.copy(selectedTemplate); + $scope.selectedTemplateInvalid = false; + $scope.showPromptButton = false; + $scope.promptModalMissingReqFields = false; + } + }; + + const setupNodeForm = () => { + $scope.nodeFormDataLoaded = false; + $scope.template_queryset = { + page_size: '5', + order_by: 'name', + type: 'workflow_job_template,job_template' + }; + + $scope.templates = []; + $scope.template_dataset = {}; + + // Go out and GET the list contents for each of the tabs + + listPromises.push( + qs.search(GetBasePath('unified_job_templates'), $scope.template_queryset) + .then(function(res) { + $scope.template_dataset = res.data; + $scope.templates = $scope.template_dataset.results; + }) + ); + + $scope.project_queryset = { + page_size: '5', + order_by: 'name' + }; + + $scope.projects = []; + $scope.project_dataset = {}; + + listPromises.push( + qs.search(GetBasePath('projects'), $scope.project_queryset) + .then(function(res) { + $scope.project_dataset = res.data; + $scope.projects = $scope.project_dataset.results; + }) + ); + + $scope.inventory_source_dataset = { + page_size: '5', + order_by: 'name', + not__source: '' + } + + $scope.inventory_sources = []; + $scope.inventory_source_dataset = {}; + + listPromises.push( + qs.search(GetBasePath('inventory_sources'), $scope.inventory_source_dataset) + .then(function(res) { + $scope.inventory_source_dataset = res.data; + $scope.inventory_sources = $scope.inventory_source_dataset.results; + }) + ); + + $q.all(listPromises) + .then(() => { + if (!$scope.node.isNew && !$scope.node.edited && $scope.node.unifiedJobTemplate && $scope.node.unifiedJobTemplate.unified_job_type && $scope.node.unifiedJobTemplate.unified_job_type === 'job') { + // This is a node that we got back from the api with an incomplete + // unified job template so we're going to pull down the whole object + + TemplatesService.getUnifiedJobTemplate($scope.node.unifiedJobTemplate.id) + .then(function(data) { + $scope.node.unifiedJobTemplate = _.clone(data.data.results[0]); + finishConfiguringEdit(); + }, function(error) { + ProcessErrors($scope, error.data, error.status, null, { + hdr: 'Error!', + msg: 'Failed to get unified job template. GET returned ' + + 'status: ' + error.status + }); + }); + } else { + finishConfiguringEdit(); + } + }); + } + + setupNodeForm(); } ]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js index 197b6ae86b..2a273a4e0f 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js @@ -9,13 +9,16 @@ import workflowNodeFormController from './workflow-node-form.controller'; export default ['templateUrl', function(templateUrl) { return { - scope: {}, + scope: { + mode: '<', + node: '=', + cancel: '&', + select: '&', + readOnly: '<' + }, restrict: 'E', templateUrl: templateUrl('templates/workflows/workflow-maker/forms/workflow-node-form'), - controller: workflowNodeFormController, - link: function(scope) { - console.log('inside link function for workflow node form'); - } + controller: workflowNodeFormController }; } ]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html index c35a26575a..c6c239f405 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html @@ -1,17 +1,111 @@ -
-
{{strings.get('workflow_maker.JOBS')}}
-
{{strings.get('workflow_maker.PROJECT_SYNC')}}
-
{{strings.get('workflow_maker.INVENTORY_SYNC')}}
-
-
-
-
-
-
- +
+
{{mode === 'edit' ? node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_TEMPLATE')}}
+
+
{{strings.get('workflow_maker.JOBS')}}
+
{{strings.get('workflow_maker.PROJECT_SYNC')}}
+
{{strings.get('workflow_maker.INVENTORY_SYNC')}}
+
+
+
+
+ + +
+ +
+
No records matched your search.
+
+
PLEASE ADD ITEMS TO THIS LIST
+
+ + + + + + + + + + + + + + + +
+ +
+ + + {{ template.name }}
+
+ +
+
+
+ + +
+ +
+
No records matched your search.
+
+
No Projects Have Been Created
+
+ + + + + + + + + + + + + +
+
+ + + {{ project.name }}
+
+ +
+
+
+ + +
+ +
+
No records matched your search.
+
+
PLEASE ADD ITEMS TO THIS LIST
+
+ + + + + + + + + + + + + +
+
+ + + {{ inventory_source.name }}
+
+ +
+
@@ -24,28 +118,29 @@ {{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }}
-
-
diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 580d1aa231..3dd307a426 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -13,22 +13,26 @@ display: flex; height: 34px; } + .WorkflowMaker-title { align-items: center; flex: 1 0 auto; display: flex; height: 34px; } + .WorkflowMaker-titleText { color: @list-title-txt; font-size: 14px; font-weight: bold; margin-right: 10px; } + .WorkflowMaker-exitHolder { justify-content: flex-end; display: flex; } + .WorkflowMaker-exit{ cursor:pointer; padding:0px; @@ -40,9 +44,11 @@ transition: color 0.2s; line-height:1; } + .WorkflowMaker-exit:hover{ color:@default-icon; } + .WorkflowMaker-contentHolder { display: flex; border: 1px solid @b7grey; @@ -50,11 +56,13 @@ height: ~"calc(100% - 85px)"; overflow: hidden; } + .WorkflowMaker-contentLeft { flex: 1; flex-direction: column; height: 100%; } + .WorkflowMaker-contentRight { flex: 0 0 400px; border-left: 1px solid @b7grey; @@ -63,12 +71,14 @@ height: 100%; overflow-y: scroll; } + .WorkflowMaker-buttonHolder { height: 30px; display: flex; justify-content: flex-end; margin-top: 20px; } + .WorkflowMaker-saveButton{ background-color: @submit-button-bg; color: @submit-button-text; @@ -117,45 +127,55 @@ justify-content: center; border-radius: 4px; } + .WorkflowMaker-deleteModal { height: 200px; width: 600px; background-color: @default-bg; border-radius: 5px; } + .WorkflowMaker-formTitle { color: @list-title-txt; font-size: 14px; font-weight: bold; margin-bottom: 20px; } + .WorkflowMaker-formHelp { color: @default-interface-txt; } + .WorkflowMaker-formLists { margin-bottom: 20px; .SmartSearch-searchTermContainer { width: 100%; } } + .WorkflowMaker-formTitle { display: flex; color: @default-interface-txt; margin-right: 10px; } + .WorkflowMaker-formLabel { font-weight: normal; } + .WorkflowMaker-formElement { margin-bottom: 10px; } + .WorkflowMaker-chart { display: flex; width: 100%; } + .WorkflowMaker-totalJobs { margin-right: 5px; } + .WorkflowLegend-maker { display: flex; height: 40px; @@ -164,33 +184,39 @@ background: @default-bg; border-bottom: 1px solid @b7grey; } + .WorkflowLegend-maker--left { flex: 1 0 auto; } + .WorkflowLegend-maker--right { flex: 0 0 217px; text-align: right; padding-right: 20px; position: relative; } + .WorkflowLegend-onSuccessLegend { height: 4px; width: 20px; background-color: @submit-button-bg; margin: 18px 5px 18px 0px; } + .WorkflowLegend-onFailLegend { height: 4px; width: 20px; background-color: @default-err; margin: 18px 5px 18px 0px; } + .WorkflowLegend-alwaysLegend { height: 4px; width: 20px; background-color: @default-link; margin: 18px 5px 18px 0px; } + .WorkflowLegend-letterCircle{ border-radius: 50%; width: 20px; @@ -201,6 +227,7 @@ margin: 10px 5px 10px 0px; line-height: 20px; } + .WorkflowLegend-details { align-items: center; display: flex; @@ -215,6 +242,7 @@ display: block; flex: 1 0 auto; } + .WorkflowLegend-details--right { flex: 0 0 44px; text-align: right; @@ -229,15 +257,18 @@ font-size: 1.2em; margin-left: 15px; } + .Key-menuIcon:hover, .WorkflowMaker-manualControlsIcon:hover { color: @default-link-hov; cursor: pointer; } + .Key-menuIcon--active, .WorkflowMaker-manualControlsIcon--active { color: @default-link-hov; } + .WorkflowMaker-manualControls { position: absolute; left: -106px; @@ -251,6 +282,7 @@ margin-left: -1px; border-right: 0; } + .WorkflowLegend-manualControls { position: absolute; left: -272px; @@ -262,13 +294,16 @@ border: 1px solid @d7grey; border-bottom-left-radius: 5px; } + .WorkflowMaker-formTab { margin-right: 10px; } + .WorkflowMaker-preventBodyScrolling { height: 100%; overflow: hidden; } + .WorkflowMaker-invalidJobTemplateWarning { margin-bottom: 5px; color: @default-err; @@ -281,15 +316,18 @@ background-color: @default-bg; border: 1px solid @default-list-header-bg; } + .Key-listItem { display: flex; padding: 0; margin: 5px 0 0 0; } + .Key-listItemContent { margin: 0; line-height: 20px; } + .Key-listItemContent--circle { line-height: 28px; } @@ -300,27 +338,34 @@ margin: 9px 5px 9px 0px; outline: none; } + .Key-heading { font-weight: 700; margin: 0 0 10px; line-height: 0; padding: 0; } + .Key-icon--success { background-color: @submit-button-bg; } + .Key-icon--fail { background-color: @default-err; } + .Key-icon--always { background-color: @default-link; } + .Key-icon--warning { background: @default-warning; } + .Key-icon--default { background: @default-icon; } + .Key-icon--circle { border-radius: 50%; width: 20px; @@ -330,6 +375,7 @@ line-height: 20px; margin: 4px 5px 5px 0px; } + .Key-details { display: flex; height: 40px; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js index c3b52b74a1..c1fa993396 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js @@ -11,27 +11,11 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', ProcessErrors, CreateSelect2, $q, JobTemplate, WorkflowJobTemplate, Empty, PromptService, Rest, TemplatesStrings, $timeout) { - let promptWatcher, surveyQuestionWatcher, credentialsWatcher; - $scope.strings = TemplatesStrings; + // TODO: I don't think this needs to be on scope but changing it will require changes to + // all the prompt places $scope.preventCredsWithPasswords = true; - $scope.workflowMakerFormConfig = { - nodeMode: "idle", - activeTab: "jobs", - formIsValid: false - }; - - $scope.job_type_options = [{ - label: $scope.strings.get('workflow_maker.RUN'), - value: "run" - }, { - label: $scope.strings.get('workflow_maker.CHECK'), - value: "check" - }]; - - $scope.edgeTypeOptions = createEdgeTypeOptions(); - let editRequests = []; let associateRequests = []; let disassociateRequests = []; @@ -41,32 +25,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', $scope.toggleKey = () => $scope.showKey = !$scope.showKey; $scope.keyClassList = `{ 'Key-menuIcon--active': showKey }`; - function createEdgeTypeOptions() { - return ([{ - label: $scope.strings.get('workflow_maker.ALWAYS'), - value: 'always' - }, - { - label: $scope.strings.get('workflow_maker.ON_SUCCESS'), - value: 'success' - }, - { - label: $scope.strings.get('workflow_maker.ON_FAILURE'), - value: 'failure' - } - ]); - } - - function resetNodeForm() { - $scope.workflowMakerFormConfig.nodeMode = "idle"; - delete $scope.selectedTemplate; - delete $scope.placeholderNode; - delete $scope.betweenTwoNodes; - $scope.nodeBeingEdited = null; - $scope.workflowMakerFormConfig.activeTab = "jobs"; - - $scope.$broadcast('clearWorkflowLists'); - } + $scope.formState = { + 'showNodeForm': false, + 'showLinkForm': false + }; function recursiveNodeUpdates(params, completionCallback) { // params.parentId @@ -301,62 +263,7 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', } } - let updateEdgeDropdownOptions = (edgeTypeValue) => { - // Not passing an edgeTypeValue will include all by default - - if (edgeTypeValue) { - $scope.edgeTypeOptions = _.filter(createEdgeTypeOptions(), { - 'value': edgeTypeValue - }); - } else { - $scope.edgeTypeOptions = createEdgeTypeOptions(); - } - - CreateSelect2({ - element: '#workflow_node_edge', - multiple: false - }); - }; - - let checkCredentialsForRequiredPasswords = () => { - let credentialRequiresPassword = false; - $scope.promptData.prompts.credentials.value.forEach((credential) => { - if ((credential.passwords_needed && - credential.passwords_needed.length > 0) || - (_.has(credential, 'inputs.vault_password') && - credential.inputs.vault_password === "ASK") - ) { - credentialRequiresPassword = true; - } - }); - $scope.credentialRequiresPassword = credentialRequiresPassword; - }; - - let watchForPromptChanges = () => { - let promptDataToWatch = [ - 'promptData.prompts.inventory.value', - 'promptData.prompts.verbosity.value', - 'missingSurveyValue' - ]; - - promptWatcher = $scope.$watchGroup(promptDataToWatch, function () { - let missingPromptValue = false; - if ($scope.missingSurveyValue) { - missingPromptValue = true; - } else if ($scope.selectedTemplate.type === 'job_template' && (!$scope.promptData.prompts.inventory.value || !$scope.promptData.prompts.inventory.value.id)) { - missingPromptValue = true; - } - $scope.promptModalMissingReqFields = missingPromptValue; - }); - - if ($scope.promptData.launchConf.ask_credential_on_launch && $scope.credentialRequiresPassword) { - credentialsWatcher = $scope.$watch('promptData.prompts.credentials', () => { - checkCredentialsForRequiredPasswords(); - }); - } - }; - - $scope.closeWorkflowMaker = function () { + $scope.closeWorkflowMaker = function() { // Revert the data to the master which was created when the dialog was opened $scope.treeData.data = angular.copy($scope.treeDataMaster); $scope.closeDialog(); @@ -440,19 +347,15 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', $scope.startAddNode = function (parent, betweenTwoNodes) { - if ($scope.placeholderNode || $scope.nodeBeingEdited) { + if ($scope.nodeBeingWorkedOn) { $scope.cancelNodeForm(); } - if ($scope.linkBeingEdited) { + if ($scope.linkBeingWorkedOn) { $scope.cancelLinkForm(); } - $scope.workflowMakerFormConfig.nodeMode = "add"; - $scope.addParent = parent; - $scope.betweenTwoNodes = betweenTwoNodes; - - $scope.placeholderNode = WorkflowService.addPlaceholderNode({ + $scope.nodeBeingWorkedOn = WorkflowService.addPlaceholderNode({ parent: parent, betweenTwoNodes: betweenTwoNodes, tree: $scope.treeData.data, @@ -461,440 +364,96 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', $scope.treeData.nextIndex++; - // Set the default to success - let edgeType = { - label: $scope.strings.get('workflow_maker.ON_SUCCESS'), - value: "success" - }; - - if (parent && ((betweenTwoNodes && parent.source.isStartNode) || (!betweenTwoNodes && parent.isStartNode))) { - // This node will always be executed - updateEdgeDropdownOptions('always'); - edgeType = { - label: $scope.strings.get('workflow_maker.ALWAYS'), - value: "always" - }; - } else { - updateEdgeDropdownOptions(); - } - - $scope.edgeType = edgeType; $scope.$broadcast("refreshWorkflowChart"); + $scope.nodeFormMode = "add"; + $scope.formState.showNodeForm = true; }; - $scope.confirmNodeForm = function () { - if ($scope.workflowMakerFormConfig.nodeMode === "add") { - if ($scope.selectedTemplate && $scope.edgeType && $scope.edgeType.value) { - - $scope.placeholderNode.unifiedJobTemplate = $scope.selectedTemplate; - $scope.placeholderNode.edgeType = $scope.edgeType.value; - if ($scope.placeholderNode.unifiedJobTemplate.type === 'job_template' || - $scope.placeholderNode.unifiedJobTemplate.type === 'workflow_job_template') { - $scope.placeholderNode.promptData = _.cloneDeep($scope.promptData); + $scope.confirmNodeForm = function(selectedTemplate, promptData, edgeType) { + if ($scope.nodeFormMode === "add") { + if (selectedTemplate && edgeType && edgeType.value) { + $scope.nodeBeingWorkedOn.unifiedJobTemplate = selectedTemplate; + $scope.nodeBeingWorkedOn.edgeType = edgeType.value; + if ($scope.nodeBeingWorkedOn.unifiedJobTemplate.type === 'job_template') { + $scope.nodeBeingWorkedOn.promptData = _.cloneDeep(promptData); } - $scope.placeholderNode.canEdit = true; + $scope.nodeBeingWorkedOn.canEdit = true; - delete $scope.placeholderNode.placeholder; - - resetNodeForm(); + delete $scope.nodeBeingWorkedOn.placeholder; // Increment the total node counter $scope.treeData.data.totalNodes++; } - } else if ($scope.workflowMakerFormConfig.nodeMode === "edit") { - if ($scope.selectedTemplate && $scope.edgeType && $scope.edgeType.value) { - $scope.nodeBeingEdited.unifiedJobTemplate = $scope.selectedTemplate; - $scope.nodeBeingEdited.edgeType = $scope.edgeType.value; - if ($scope.nodeBeingEdited.unifiedJobTemplate.type === 'job_template' || $scope.nodeBeingEdited.unifiedJobTemplate.type === 'workflow_job_template') { - $scope.nodeBeingEdited.promptData = _.cloneDeep($scope.promptData); + } else if ($scope.nodeFormMode === "edit") { + if (selectedTemplate) { + $scope.nodeBeingWorkedOn.unifiedJobTemplate = selectedTemplate; + + if ($scope.nodeBeingWorkedOn.unifiedJobTemplate.type === 'job_template') { + $scope.nodeBeingWorkedOn.promptData = _.cloneDeep(promptData); } - $scope.nodeBeingEdited.isActiveEdit = false; + $scope.nodeBeingWorkedOn.isActiveEdit = false; - $scope.nodeBeingEdited.edited = true; - - resetNodeForm(); + $scope.nodeBeingWorkedOn.edited = true; } } - if (promptWatcher) { - promptWatcher(); - } - - if (surveyQuestionWatcher) { - surveyQuestionWatcher(); - } - - if (credentialsWatcher) { - credentialsWatcher(); - } - - $scope.promptData = null; + $scope.formState.showNodeForm = false; + $scope.nodeFormMode = null; + $scope.nodeBeingWorkedOn = null; $scope.$broadcast("refreshWorkflowChart"); }; - $scope.cancelNodeForm = function () { - if ($scope.workflowMakerFormConfig.nodeMode === "add") { + $scope.cancelNodeForm = function() { + if ($scope.nodeFormMode === "add") { // Remove the placeholder node from the tree WorkflowService.removeNodeFromTree({ tree: $scope.treeData.data, - nodeToBeDeleted: $scope.placeholderNode + nodeToBeDeleted: $scope.nodeBeingWorkedOn }); - } else if ($scope.workflowMakerFormConfig.nodeMode === "edit") { - $scope.nodeBeingEdited.isActiveEdit = false; + } else if ($scope.nodeFormMode === "edit") { + $scope.nodeBeingWorkedOn.isActiveEdit = false; } - - if (promptWatcher) { - promptWatcher(); - } - - if (surveyQuestionWatcher) { - surveyQuestionWatcher(); - } - - if (credentialsWatcher) { - credentialsWatcher(); - } - - $scope.promptData = null; - $scope.selectedTemplateInvalid = false; - $scope.showPromptButton = false; - - // Reset the form - resetNodeForm(); - + $scope.formState.showNodeForm = false; + $scope.nodeBeingWorkedOn = null; + $scope.nodeFormMode = null; $scope.$broadcast("refreshWorkflowChart"); }; /* EDIT NODE FUNCTIONS */ - $scope.startEditNode = function (nodeToEdit) { - - if ($scope.linkBeingEdited) { + $scope.startEditNode = function(nodeToEdit) { + if ($scope.linkBeingWorkedOn) { $scope.cancelLinkForm(); } - if (!$scope.nodeBeingEdited || ($scope.nodeBeingEdited && $scope.nodeBeingEdited.id !== nodeToEdit.id)) { - if ($scope.placeholderNode || $scope.nodeBeingEdited) { + if (!$scope.nodeBeingWorkedOn || ($scope.nodeBeingWorkedOn && $scope.nodeBeingWorkedOn.id !== nodeToEdit.id)) { + if ($scope.nodeBeingWorkedOn) { $scope.cancelNodeForm(); - - // Refresh this object as the parent has changed - nodeToEdit = WorkflowService.searchTree({ - element: $scope.treeData.data, - matchingId: nodeToEdit.id - }); } - $scope.workflowMakerFormConfig.nodeMode = "edit"; + $scope.nodeFormMode = "edit"; + + $scope.formState.showNodeForm = true; let parent = WorkflowService.searchTree({ element: $scope.treeData.data, matchingId: nodeToEdit.parent.id }); - $scope.nodeBeingEdited = WorkflowService.searchTree({ + $scope.nodeBeingWorkedOn = WorkflowService.searchTree({ element: parent, matchingId: nodeToEdit.id }); - $scope.nodeBeingEdited.isActiveEdit = true; - - let finishConfiguringEdit = function () { - let templateType = $scope.nodeBeingEdited.unifiedJobTemplate.type; - let jobTemplate = templateType === "workflow_job_template" ? new WorkflowJobTemplate() : new JobTemplate(); - if (!_.isEmpty($scope.nodeBeingEdited.promptData)) { - $scope.promptData = _.cloneDeep($scope.nodeBeingEdited.promptData); - const launchConf = $scope.promptData.launchConf; - - if (!launchConf.survey_enabled && - !launchConf.ask_inventory_on_launch && - !launchConf.ask_credential_on_launch && - !launchConf.ask_verbosity_on_launch && - !launchConf.ask_job_type_on_launch && - !launchConf.ask_limit_on_launch && - !launchConf.ask_tags_on_launch && - !launchConf.ask_skip_tags_on_launch && - !launchConf.ask_diff_mode_on_launch && - !launchConf.credential_needed_to_start && - !launchConf.ask_variables_on_launch && - launchConf.variables_needed_to_start.length === 0) { - $scope.showPromptButton = false; - $scope.promptModalMissingReqFields = false; - } else { - $scope.showPromptButton = true; - - if ($scope.nodeBeingEdited.unifiedJobTemplate.type === 'job_template' && launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeBeingEdited.originalNodeObj.summary_fields.inventory')) { - $scope.promptModalMissingReqFields = true; - } else { - $scope.promptModalMissingReqFields = false; - } - } - } else if ( - _.get($scope, 'nodeBeingEdited.unifiedJobTemplate.unified_job_type') === 'job' || - _.get($scope, 'nodeBeingEdited.unifiedJobTemplate.type') === 'job_template' || - _.get($scope, 'nodeBeingEdited.unifiedJobTemplate.unified_job_type') === 'workflow_job' || - _.get($scope, 'nodeBeingEdited.unifiedJobTemplate.type') === 'workflow_job_template' - ) { - let promises = [jobTemplate.optionsLaunch($scope.nodeBeingEdited.unifiedJobTemplate.id), jobTemplate.getLaunch($scope.nodeBeingEdited.unifiedJobTemplate.id)]; - - if (_.has($scope, 'nodeBeingEdited.originalNodeObj.related.credentials')) { - Rest.setUrl($scope.nodeBeingEdited.originalNodeObj.related.credentials); - promises.push(Rest.get()); - } - - $q.all(promises) - .then((responses) => { - let launchOptions = responses[0].data, - launchConf = responses[1].data, - workflowNodeCredentials = responses[2] ? responses[2].data.results : []; - - let prompts = PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data, - currentValues: $scope.nodeBeingEdited.originalNodeObj - }); - - let defaultCredsWithoutOverrides = []; - - prompts.credentials.previousOverrides = _.cloneDeep(workflowNodeCredentials); - - const credentialHasScheduleOverride = (templateDefaultCred) => { - let credentialHasOverride = false; - workflowNodeCredentials.forEach((scheduleCred) => { - if (templateDefaultCred.credential_type === scheduleCred.credential_type) { - if ( - (!templateDefaultCred.vault_id && !scheduleCred.inputs.vault_id) || - (templateDefaultCred.vault_id && scheduleCred.inputs.vault_id && templateDefaultCred.vault_id === scheduleCred.inputs.vault_id) - ) { - credentialHasOverride = true; - } - } - }); - - return credentialHasOverride; - }; - - if (_.has(launchConf, 'defaults.credentials')) { - launchConf.defaults.credentials.forEach((defaultCred) => { - if (!credentialHasScheduleOverride(defaultCred)) { - defaultCredsWithoutOverrides.push(defaultCred); - } - }); - } - - prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides); - - if ($scope.nodeBeingEdited.unifiedJobTemplate.unified_job_template === 'job') { - if ((!$scope.nodeBeingEdited.unifiedJobTemplate.inventory && !launchConf.ask_inventory_on_launch) || !$scope.nodeBeingEdited.unifiedJobTemplate.project) { - $scope.selectedTemplateInvalid = true; - } else { - $scope.selectedTemplateInvalid = false; - } - } else { - $scope.selectedTemplateInvalid = false; - } - - let credentialRequiresPassword = false; - - prompts.credentials.value.forEach((credential) => { - if (credential.inputs) { - if ((credential.inputs.password && credential.inputs.password === "ASK") || - (credential.inputs.become_password && credential.inputs.become_password === "ASK") || - (credential.inputs.ssh_key_unlock && credential.inputs.ssh_key_unlock === "ASK") || - (credential.inputs.vault_password && credential.inputs.vault_password === "ASK") - ) { - credentialRequiresPassword = true; - } - } else if (credential.passwords_needed && credential.passwords_needed.length > 0) { - credentialRequiresPassword = true; - } - }); - - $scope.credentialRequiresPassword = credentialRequiresPassword; - - if (!launchConf.survey_enabled && - !launchConf.ask_inventory_on_launch && - !launchConf.ask_credential_on_launch && - !launchConf.ask_verbosity_on_launch && - !launchConf.ask_job_type_on_launch && - !launchConf.ask_limit_on_launch && - !launchConf.ask_tags_on_launch && - !launchConf.ask_skip_tags_on_launch && - !launchConf.ask_diff_mode_on_launch && - !launchConf.credential_needed_to_start && - !launchConf.ask_variables_on_launch && - launchConf.variables_needed_to_start.length === 0) { - $scope.showPromptButton = false; - $scope.promptModalMissingReqFields = false; - } else { - $scope.showPromptButton = true; - - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeBeingEdited.originalNodeObj.summary_fields.inventory')) { - $scope.promptModalMissingReqFields = true; - } else { - $scope.promptModalMissingReqFields = false; - } - - if (responses[1].data.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions($scope.nodeBeingEdited.unifiedJobTemplate.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec, - extra_data: _.cloneDeep($scope.nodeBeingEdited.originalNodeObj.extra_data) - }); - - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); - - $scope.nodeBeingEdited.promptData = $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - surveyQuestions: surveyQuestionRes.data.spec, - template: $scope.nodeBeingEdited.unifiedJobTemplate.id - }; - - surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - - checkCredentialsForRequiredPasswords(); - - watchForPromptChanges(); - }); - } else { - $scope.nodeBeingEdited.promptData = $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - template: $scope.nodeBeingEdited.unifiedJobTemplate.id - }; - - checkCredentialsForRequiredPasswords(); - - watchForPromptChanges(); - } - } - }); - } - - if (_.get($scope, 'nodeBeingEdited.unifiedJobTemplate')) { - - if (_.get($scope, 'nodeBeingEdited.unifiedJobTemplate.type') === "job_template" || - _.get($scope, 'nodeBeingEdited.unifiedJobTemplate.type') === "workflow_job_template") { - $scope.workflowMakerFormConfig.activeTab = "jobs"; - } - - $scope.selectedTemplate = $scope.nodeBeingEdited.unifiedJobTemplate; - - if ($scope.selectedTemplate.unified_job_type) { - switch ($scope.selectedTemplate.unified_job_type) { - case "job": - case "workflow_job": - $scope.workflowMakerFormConfig.activeTab = "jobs"; - break; - case "project_update": - $scope.workflowMakerFormConfig.activeTab = "project_sync"; - break; - case "inventory_update": - $scope.workflowMakerFormConfig.activeTab = "inventory_sync"; - break; - } - } else if ($scope.selectedTemplate.type) { - switch ($scope.selectedTemplate.type) { - case "job_template": - case "workflow_job_template": - $scope.workflowMakerFormConfig.activeTab = "jobs"; - break; - case "project": - $scope.workflowMakerFormConfig.activeTab = "project_sync"; - break; - case "inventory_source": - $scope.workflowMakerFormConfig.activeTab = "inventory_sync"; - break; - } - } - } else { - $scope.workflowMakerFormConfig.activeTab = "jobs"; - } - - let edgeDropdownOptions = null; - - // Select RUN dropdown option - switch ($scope.nodeBeingEdited.edgeType) { - case "always": - $scope.edgeType = { - label: $scope.strings.get('workflow_maker.ALWAYS'), - value: "always" - }; - if ($scope.nodeBeingEdited.isRoot) { - edgeDropdownOptions = 'always'; - } - break; - case "success": - $scope.edgeType = { - label: $scope.strings.get('workflow_maker.ON_SUCCESS'), - value: "success" - }; - break; - case "failure": - $scope.edgeType = { - label: $scope.strings.get('workflow_maker.ON_FAILURE'), - value: "failure" - }; - break; - } - - $timeout(updateEdgeDropdownOptions(edgeDropdownOptions)); - - $scope.$broadcast("refreshWorkflowChart"); - }; - - // Determine whether or not we need to go out and GET this nodes unified job template - // in order to determine whether or not prompt fields are needed - if (!$scope.nodeBeingEdited.isNew && !$scope.nodeBeingEdited.edited && - (_.get($scope, 'nodeBeingEdited.unifiedJobTemplate.unified_job_type') === 'job' || - _.get($scope, 'nodeBeingEdited.unifiedJobTemplate.unified_job_type') === 'workflow_job')) { - // This is a node that we got back from the api with an incomplete - // unified job template so we're going to pull down the whole object - - TemplatesService.getUnifiedJobTemplate($scope.nodeBeingEdited.unifiedJobTemplate.id) - .then(function (data) { - $scope.nodeBeingEdited.unifiedJobTemplate = _.clone(data.data.results[0]); - finishConfiguringEdit(); - }, function ({ - data, - status, - config - }) { - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER'), - msg: $scope.strings.get('error.CALL', { - path: `${config.url}`, - action: `${config.method}`, - status - }) - }); - }); - } else { - finishConfiguringEdit(); - } - + $scope.nodeBeingWorkedOn.isActiveEdit = true; } - }; + $scope.$broadcast("refreshWorkflowChart"); + } /* EDIT LINK FUNCTIONS */ @@ -902,18 +461,17 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', const setupLinkEdit = () => { const parentNode = WorkflowService.searchTree({ element: $scope.treeData.data, - matchingId: parentId, - byNodeId: true + matchingId: parentId }); parentNode.isLinkEditParent = true; // Loop across children looking for childId - const childNode = _.find(parentNode.children, {'nodeId': childId}); + const childNode = _.find(parentNode.children, {'id': childId}); childNode.isLinkEditChild = true; - $scope.linkBeingEdited = { + $scope.linkBeingWorkedOn = { parent: parentNode, child: childNode } @@ -929,19 +487,19 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', }, edgeType: childNode.edgeType } - $scope.editLink = true; + $scope.formState.showLinkForm = true; $scope.$broadcast("refreshWorkflowChart"); } - if ($scope.nodeBeingEdited || $scope.placeholderNode) { + if ($scope.nodeBeingWorkedOn) { $scope.cancelNodeForm(); } - if ($scope.linkBeingEdited) { - if ($scope.linkBeingEdited.parent.nodeId !== parentId || $scope.linkBeingEdited.child.nodeId !== childId) { - $scope.linkBeingEdited.parent.isLinkEditParent = false; - $scope.linkBeingEdited.child.isLinkEditChild = false; + if ($scope.linkBeingWorkedOn) { + if ($scope.linkBeingWorkedOn.parent.nodeId !== parentId || $scope.linkBeingWorkedOn.child.nodeId !== childId) { + $scope.linkBeingWorkedOn.parent.isLinkEditParent = false; + $scope.linkBeingWorkedOn.child.isLinkEditChild = false; setupLinkEdit() } } else { @@ -950,34 +508,20 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', }; - $scope.confirmLinkForm = (parentId, childId, edgeType) => { - $scope.linkBeingEdited.parent.isLinkEditParent = false; - $scope.linkBeingEdited.child.isLinkEditChild = false; - const parentNode = WorkflowService.searchTree({ - element: $scope.treeData.data, - matchingId: parentId, - byNodeId: true - }); - - // Loop across children looking for childId - const childNode = _.find(parentNode.children, {'nodeId': childId}); - - childNode.edgeType = edgeType; - - $scope.linkBeingEdited = null; - - $scope.editLink = false; - + $scope.confirmLinkForm = (newEdgeType) => { + $scope.linkBeingWorkedOn.parent.isLinkEditParent = false; + $scope.linkBeingWorkedOn.child.isLinkEditChild = false; + $scope.linkBeingWorkedOn.child.edgeType = newEdgeType; + $scope.linkBeingWorkedOn = null; + $scope.formState.showLinkForm = false; $scope.$broadcast("refreshWorkflowChart"); } $scope.cancelLinkForm = () => { - $scope.linkBeingEdited.parent.isLinkEditParent = false; - $scope.linkBeingEdited.child.isLinkEditChild = false; - $scope.linkBeingEdited = null; - - $scope.editLink = false; - + $scope.linkBeingWorkedOn.parent.isLinkEditParent = false; + $scope.linkBeingWorkedOn.child.isLinkEditChild = false; + $scope.linkBeingWorkedOn = null; + $scope.formState.showLinkForm = false; $scope.$broadcast("refreshWorkflowChart"); }; @@ -1000,7 +544,7 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', $scope.confirmDeleteNode = function () { if ($scope.nodeToBeDeleted) { - if ($scope.linkBeingEdited) { + if ($scope.linkBeingWorkedOn) { $scope.cancelLinkForm(); } @@ -1015,195 +559,16 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', $scope.treeData.data.deletedNodes.push($scope.nodeToBeDeleted.nodeId); } - if ($scope.nodeToBeDeleted.isActiveEdit) { - resetNodeForm(); - } - resetDeleteNode(); $scope.$broadcast("refreshWorkflowChart"); - if ($scope.placeholderNode) { - let edgeType = { - label: $scope.strings.get('workflow_maker.ON_SUCCESS'), - value: "success" - }; - - if ($scope.placeholderNode.isRoot) { - updateEdgeDropdownOptions('always'); - edgeType = { - label: $scope.strings.get('workflow_maker.ALWAYS'), - value: "always" - }; - } else { - updateEdgeDropdownOptions(); - } - - $scope.edgeType = edgeType; - } else if ($scope.nodeBeingEdited) { - - switch ($scope.nodeBeingEdited.edgeType) { - case "always": - $scope.edgeType = { - label: $scope.strings.get('workflow_maker.ALWAYS'), - value: "always" - }; - if ($scope.nodeBeingEdited.isRoot) { - updateEdgeDropdownOptions('always'); - } else { - updateEdgeDropdownOptions(); - } - break; - case "success": - $scope.edgeType = { - label: $scope.strings.get('workflow_maker.ON_SUCCESS'), - value: "success" - }; - updateEdgeDropdownOptions(); - break; - case "failure": - $scope.edgeType = { - label: $scope.strings.get('workflow_maker.ON_FAILURE'), - value: "failure" - }; - updateEdgeDropdownOptions(); - break; - } - } - $scope.treeData.data.totalNodes--; } }; - $scope.toggleFormTab = function (tab) { - if ($scope.workflowMakerFormConfig.activeTab !== tab) { - $scope.workflowMakerFormConfig.activeTab = tab; - } - }; - - $scope.templateManuallySelected = function (selectedTemplate) { - - if (promptWatcher) { - promptWatcher(); - } - - if (surveyQuestionWatcher) { - surveyQuestionWatcher(); - } - - if (credentialsWatcher) { - credentialsWatcher(); - } - - $scope.promptData = null; - if (selectedTemplate.type === "job_template" || selectedTemplate.type === "workflow_job_template") { - let jobTemplate = selectedTemplate.type === "workflow_job_template" ? new WorkflowJobTemplate() : new JobTemplate(); - - $q.all([jobTemplate.optionsLaunch(selectedTemplate.id), jobTemplate.getLaunch(selectedTemplate.id)]) - .then((responses) => { - let launchConf = responses[1].data; - if (selectedTemplate.type === 'job_template') { - if ((!selectedTemplate.inventory && !launchConf.ask_inventory_on_launch) || !selectedTemplate.project) { - $scope.selectedTemplateInvalid = true; - } else { - $scope.selectedTemplateInvalid = false; - } - - if (launchConf.passwords_needed_to_start && launchConf.passwords_needed_to_start.length > 0) { - $scope.credentialRequiresPassword = true; - } else { - $scope.credentialRequiresPassword = false; - } - } - - $scope.selectedTemplate = angular.copy(selectedTemplate); - - if (!launchConf.survey_enabled && - !launchConf.ask_inventory_on_launch && - !launchConf.ask_credential_on_launch && - !launchConf.ask_verbosity_on_launch && - !launchConf.ask_job_type_on_launch && - !launchConf.ask_limit_on_launch && - !launchConf.ask_tags_on_launch && - !launchConf.ask_skip_tags_on_launch && - !launchConf.ask_diff_mode_on_launch && - !launchConf.credential_needed_to_start && - !launchConf.ask_variables_on_launch && - launchConf.variables_needed_to_start.length === 0) { - $scope.showPromptButton = false; - $scope.promptModalMissingReqFields = false; - } else { - $scope.showPromptButton = true; - - if (selectedTemplate.type === 'job_template') { - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory')) { - $scope.promptModalMissingReqFields = true; - } else { - $scope.promptModalMissingReqFields = false; - } - } else { - $scope.promptModalMissingReqFields = false; - } - - if (launchConf.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions(selectedTemplate.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec - }); - - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - surveyQuestions: processed.surveyQuestions, - template: selectedTemplate.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - - watchForPromptChanges(); - }); - } else { - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - template: selectedTemplate.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - watchForPromptChanges(); - } - } - }); - } else { - $scope.selectedTemplate = angular.copy(selectedTemplate); - $scope.selectedTemplateInvalid = false; - $scope.showPromptButton = false; - $scope.promptModalMissingReqFields = false; - } - }; - - $scope.toggleManualControls = function () { + $scope.toggleManualControls = function() { $scope.showManualControls = !$scope.showManualControls; }; @@ -1233,10 +598,6 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', $scope.$broadcast('zoomToFitChart'); }; - $scope.openPromptModal = function () { - $scope.promptData.triggerModalOpen = true; - }; - let allNodes = []; let page = 1; @@ -1275,11 +636,7 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', // This is the last page buildTreeFromNodes(); } - }, function ({ - data, - status, - config - }) { + }, function ({ data, status, config }) { ProcessErrors($scope, data, status, null, { hdr: $scope.strings.get('error.HEADER'), msg: $scope.strings.get('error.CALL', { @@ -1292,7 +649,5 @@ export default ['$scope', 'WorkflowService', 'TemplatesService', }; getNodes(); - - updateEdgeDropdownOptions(); } ]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html index b7c18669e5..aaa1c6aef0 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html @@ -84,71 +84,16 @@
- -
{{(workflowMakerFormConfig.nodeMode === 'edit' && nodeBeingEdited) ? ((nodeBeingEdited.unifiedJobTemplate && nodeBeingEdited.unifiedJobTemplate.name) ? nodeBeingEdited.unifiedJobTemplate.name : strings.get('workflow_maker.EDIT_TEMPLATE')) : strings.get('workflow_maker.ADD_A_TEMPLATE')}}
-
-
-
-
{{strings.get('workflow_maker.JOBS')}}
-
{{strings.get('workflow_maker.PROJECT_SYNC')}}
-
{{strings.get('workflow_maker.INVENTORY_SYNC')}}
-
-
-
-
-
-
- -
-
- - {{:: strings.get('workflows.INVALID_JOB_TEMPLATE') }} -
-
-
-
- - {{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }} -
-
-
- -
- -
-
-
- - - - -
-
-
+ + - - + +
- +
- diff --git a/awx/ui/client/src/workflow-results/workflow-results.block.less b/awx/ui/client/src/workflow-results/workflow-results.block.less index 3fbe218d88..60141e7a3a 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.block.less +++ b/awx/ui/client/src/workflow-results/workflow-results.block.less @@ -157,3 +157,10 @@ border-radius: 5px; font-size: 11px; } + +.WorkflowResults-rightSide .WorkflowChart-svg { + background-color: @f6grey; + border: 1px solid @d7grey; + border-top: 0px; + border-bottom-right-radius: 5px; +}