mirror of
https://github.com/ansible/awx.git
synced 2026-05-24 00:57:48 -02:30
Decouple editing a wf node with editing a node link
This commit is contained in:
@@ -116,7 +116,8 @@ function TemplatesStrings (BaseString) {
|
||||
DELETED: t.s('DELETED'),
|
||||
START: t.s('START'),
|
||||
DETAILS: t.s('DETAILS'),
|
||||
TITLE: t.s('WORKFLOW VISUALIZER')
|
||||
TITLE: t.s('WORKFLOW VISUALIZER'),
|
||||
EDIT_LINK: ({ parentName, childName }) => t.s('EDIT LINK | {{parentName}} to {{childName}}', { parentName, childName }),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
.link circle, .link .linkCross, .node .addCircle, .node .removeCircle, .node .WorkflowChart-hoverPath {
|
||||
.link circle,
|
||||
.link polygon,
|
||||
.link .linkCross,
|
||||
.node circle,
|
||||
.node .linkIcon,
|
||||
.node .WorkflowChart-hoverPath {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -18,6 +23,18 @@
|
||||
fill: @default-err-hov;
|
||||
}
|
||||
|
||||
.node .linkCircle {
|
||||
fill: @default-link;
|
||||
}
|
||||
|
||||
.node .linkIcon {
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.linkCircle.removeHovering {
|
||||
fill: @default-link-hov;
|
||||
}
|
||||
|
||||
.node {
|
||||
font-size: 12px;
|
||||
font-family: 'Open Sans', sans-serif, 'FontAwesome';
|
||||
@@ -50,8 +67,12 @@
|
||||
.WorkflowChart-alwaysShowAdd .linkCross,
|
||||
.hovering .addCircle,
|
||||
.hovering .removeCircle,
|
||||
.addHovering .betweenNodesCircle,
|
||||
.hovering .linkCircle,
|
||||
.hovering .linkIcon,
|
||||
.hovering .WorkflowChart-hoverPath,
|
||||
.hovering .linkCross {
|
||||
.addHovering .linkCross {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -136,3 +157,17 @@
|
||||
.WorkflowChart-dashedNode {
|
||||
stroke-dasharray: 5,5;
|
||||
}
|
||||
|
||||
.linkOverlay {
|
||||
fill: @default-interface-txt;
|
||||
}
|
||||
|
||||
.linkActiveEdit.linkOverlay,
|
||||
.overlayHovering .linkOverlay {
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.overlayHovering .linkPath {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
addNode: '&',
|
||||
editNode: '&',
|
||||
deleteNode: '&',
|
||||
editLink: '&',
|
||||
workflowZoomed: '&',
|
||||
mode: '@'
|
||||
},
|
||||
@@ -63,12 +64,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
});
|
||||
|
||||
line = d3.svg.line()
|
||||
.x(function (d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function (d) {
|
||||
return d.y;
|
||||
});
|
||||
.x(function(d){return d.x;})
|
||||
.y(function(d){return d.y;});
|
||||
|
||||
zoomObj = d3.behavior.zoom().scaleExtent([0.5, 2]);
|
||||
|
||||
@@ -90,7 +87,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
// This is the workflow editor
|
||||
dimensions.height = $('.WorkflowMaker-contentLeft').outerHeight() - $('.WorkflowLegend-maker').outerHeight();
|
||||
dimensions.width = $('#workflow-modal-dialog').width() - $('.WorkflowMaker-contentRight').outerWidth();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// This is the workflow details view
|
||||
let panel = $('.WorkflowResults-rightSide').children('.Panel')[0];
|
||||
let panelWidth = $(panel).width();
|
||||
@@ -113,7 +111,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
let targetX = d.target.y;
|
||||
let targetY = d.target.x + nodeH / 2;
|
||||
|
||||
let points = [{
|
||||
let points = [
|
||||
{
|
||||
x: sourceX,
|
||||
y: sourceY
|
||||
},
|
||||
@@ -131,7 +130,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
function wrap(text) {
|
||||
if(text && text.length > maxNodeTextLength) {
|
||||
return text.substring(0,maxNodeTextLength) + '...';
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -140,33 +140,17 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
var retval;
|
||||
retval = "M" + (x + r) + "," + y;
|
||||
retval += "h" + (w - 2*r);
|
||||
if (tr) {
|
||||
retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r;
|
||||
} else {
|
||||
retval += "h" + r;
|
||||
retval += "v" + r;
|
||||
}
|
||||
if (tr) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r; }
|
||||
else { retval += "h" + r; retval += "v" + r; }
|
||||
retval += "v" + (h - 2*r);
|
||||
if (br) {
|
||||
retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r;
|
||||
} else {
|
||||
retval += "v" + r;
|
||||
retval += "h" + -r;
|
||||
}
|
||||
if (br) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r; }
|
||||
else { retval += "v" + r; retval += "h" + -r; }
|
||||
retval += "h" + (2*r - w);
|
||||
if (bl) {
|
||||
retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r;
|
||||
} else {
|
||||
retval += "h" + -r;
|
||||
retval += "v" + -r;
|
||||
}
|
||||
if (bl) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r; }
|
||||
else { retval += "h" + -r; retval += "v" + -r; }
|
||||
retval += "v" + (2*r - h);
|
||||
if (tl) {
|
||||
retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r;
|
||||
} else {
|
||||
retval += "v" + -r;
|
||||
retval += "h" + r;
|
||||
}
|
||||
if (tl) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r; }
|
||||
else { retval += "v" + -r; retval += "h" + r; }
|
||||
retval += "z";
|
||||
return retval;
|
||||
}
|
||||
@@ -254,6 +238,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
// Declare the nodes
|
||||
let nodes = tree.nodes(scope.treeData),
|
||||
links = tree.links(nodes);
|
||||
|
||||
let node = svgGroup.selectAll("g.node")
|
||||
.data(nodes, function(d) {
|
||||
d.y = d.depth * 240;
|
||||
@@ -262,15 +247,9 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
|
||||
let nodeEnter = node.enter().append("g")
|
||||
.attr("class", "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 + ")";
|
||||
});
|
||||
.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 + ")"; });
|
||||
|
||||
nodeEnter.each(function(d) {
|
||||
let thisNode = d3.select(this);
|
||||
@@ -286,7 +265,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
.attr("ry", 5)
|
||||
.attr("fill", "#337ab7")
|
||||
.attr("class", "WorkflowChart-rootNode");
|
||||
} else if (d.isStartNode && scope.mode !== 'details') {
|
||||
}
|
||||
else if(d.isStartNode && scope.mode !== 'details') {
|
||||
thisNode.append("rect")
|
||||
.attr("width", rootW)
|
||||
.attr("height", rootH)
|
||||
@@ -301,11 +281,10 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
.attr("y", 30)
|
||||
.attr("dy", ".35em")
|
||||
.attr("class", "WorkflowChart-startText")
|
||||
.text(function () {
|
||||
return TemplatesStrings.get('workflow_maker.START');
|
||||
})
|
||||
.text(function () { return TemplatesStrings.get('workflow_maker.START'); })
|
||||
.call(add_node);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
thisNode.append("rect")
|
||||
.attr("width", nodeW)
|
||||
.attr("height", nodeH)
|
||||
@@ -315,12 +294,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
if(d.job && d.job.status) {
|
||||
if(d.job.status === "successful"){
|
||||
return "#5cb85c";
|
||||
} else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
|
||||
}
|
||||
else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
|
||||
return "#d9534f";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "#D7D7D7";
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "#D7D7D7";
|
||||
}
|
||||
})
|
||||
@@ -334,38 +316,18 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
thisNode.append("path")
|
||||
.attr("d", rounded_rect(1, 0, 5, nodeH, 5, 1, 0, 1, 0))
|
||||
.attr("class", "WorkflowChart-activeNode")
|
||||
.style("display", function (d) {
|
||||
return d.isActiveEdit ? null : "none";
|
||||
});
|
||||
.style("display", function(d) { return d.isActiveEdit ? null : "none"; });
|
||||
|
||||
thisNode.append("text")
|
||||
.attr("x", function (d) {
|
||||
return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2;
|
||||
})
|
||||
.attr("y", function (d) {
|
||||
return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2;
|
||||
})
|
||||
.attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
|
||||
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
|
||||
.attr("dy", ".35em")
|
||||
.attr("text-anchor", function (d) {
|
||||
return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle";
|
||||
})
|
||||
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
|
||||
.attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
|
||||
.text(function (d) {
|
||||
return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? d.unifiedJobTemplate.name : "";
|
||||
}).each(wrap);
|
||||
|
||||
thisNode.append("foreignObject")
|
||||
.attr("x", 54)
|
||||
.attr("y", 45)
|
||||
.style("font-size", "0.7em")
|
||||
.attr("class", "WorkflowChart-conflictText")
|
||||
.html(function () {
|
||||
return `<span class=\"WorkflowChart-conflictIcon\">\uf06a</span><span> ${TemplatesStrings.get('workflow_maker.EDGE_CONFLICT')}</span>`;
|
||||
})
|
||||
.style("display", function (d) {
|
||||
return (d.edgeConflict && !d.placeholder) ? null : "none";
|
||||
});
|
||||
|
||||
thisNode.append("foreignObject")
|
||||
.attr("x", 62)
|
||||
.attr("y", 22)
|
||||
@@ -375,9 +337,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
.html(function () {
|
||||
return `<span>${TemplatesStrings.get('workflow_maker.DELETED')}</span>`;
|
||||
})
|
||||
.style("display", function (d) {
|
||||
return d.unifiedJobTemplate || d.placeholder ? "none" : null;
|
||||
});
|
||||
.style("display", function(d) { return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
|
||||
|
||||
thisNode.append("circle")
|
||||
.attr("cy", nodeH)
|
||||
@@ -391,7 +351,6 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
d.unifiedJobTemplate.type === "workflow_job_template" ||
|
||||
d.unifiedJobTemplate.unified_job_type === "workflow_job") ? null : "none";
|
||||
});
|
||||
|
||||
thisNode.append("text")
|
||||
.attr("y", nodeH)
|
||||
.attr("dy", ".35em")
|
||||
@@ -493,23 +452,17 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
.attr("y", nodeH - 10)
|
||||
.attr("dy", ".35em")
|
||||
.attr("class", "WorkflowChart-detailsLink")
|
||||
.style("display", function (d) {
|
||||
return d.job && d.job.status && d.job.id ? null : "none";
|
||||
})
|
||||
.style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; })
|
||||
.text(function () {
|
||||
return TemplatesStrings.get('workflow_maker.DETAILS');
|
||||
})
|
||||
.call(details);
|
||||
thisNode.append("circle")
|
||||
.attr("id", function (d) {
|
||||
return "node-" + d.id + "-add";
|
||||
})
|
||||
.attr("id", function(d){return "node-" + d.id + "-add";})
|
||||
.attr("cx", nodeW)
|
||||
.attr("r", 10)
|
||||
.attr("class", "addCircle nodeCircle")
|
||||
.style("display", function (d) {
|
||||
return d.placeholder || !(userCanAddEdit) ? "none" : null;
|
||||
})
|
||||
.style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; })
|
||||
.call(add_node)
|
||||
.on("mouseover", function(d) {
|
||||
d3.select("#node-" + d.id)
|
||||
@@ -526,16 +479,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
thisNode.append("path")
|
||||
.attr("class", "nodeAddCross WorkflowChart-hoverPath")
|
||||
.style("fill", "white")
|
||||
.attr("transform", function () {
|
||||
return "translate(" + nodeW + "," + 0 + ")";
|
||||
})
|
||||
.attr("transform", function() { return "translate(" + nodeW + "," + 0 + ")"; })
|
||||
.attr("d", d3.svg.symbol()
|
||||
.size(60)
|
||||
.type("cross")
|
||||
)
|
||||
.style("display", function (d) {
|
||||
return d.placeholder || !(userCanAddEdit) ? "none" : null;
|
||||
})
|
||||
.style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; })
|
||||
.call(add_node)
|
||||
.on("mouseover", function(d) {
|
||||
d3.select("#node-" + d.id)
|
||||
@@ -550,16 +499,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
.classed("addHovering", false);
|
||||
});
|
||||
thisNode.append("circle")
|
||||
.attr("id", function (d) {
|
||||
return "node-" + d.id + "-remove";
|
||||
})
|
||||
.attr("id", function(d){return "node-" + d.id + "-remove";})
|
||||
.attr("cx", nodeW)
|
||||
.attr("cy", nodeH)
|
||||
.attr("r", 10)
|
||||
.attr("class", "removeCircle")
|
||||
.style("display", function (d) {
|
||||
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
})
|
||||
.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)
|
||||
@@ -576,16 +521,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
thisNode.append("path")
|
||||
.attr("class", "nodeRemoveCross WorkflowChart-hoverPath")
|
||||
.style("fill", "white")
|
||||
.attr("transform", function () {
|
||||
return "translate(" + nodeW + "," + nodeH + ") rotate(-45)";
|
||||
})
|
||||
.attr("transform", function() { return "translate(" + nodeW + "," + nodeH + ") rotate(-45)"; })
|
||||
.attr("d", d3.svg.symbol()
|
||||
.size(60)
|
||||
.type("cross")
|
||||
)
|
||||
.style("display", function (d) {
|
||||
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
})
|
||||
.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)
|
||||
@@ -599,6 +540,50 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
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 `<span class="fa fa-link" />`;
|
||||
// })
|
||||
// .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) {
|
||||
@@ -633,9 +618,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
|
||||
return statusClass;
|
||||
})
|
||||
.style("display", function (d) {
|
||||
return d.job && d.job.status ? null : "none";
|
||||
})
|
||||
.style("display", function(d) { return d.job && d.job.status ? null : "none"; })
|
||||
.attr("cy", 10)
|
||||
.attr("cx", 10)
|
||||
.attr("r", 6);
|
||||
@@ -652,13 +635,12 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
let paddedElapsedMoment = Math.floor(elapsedMoment.asHours()) < 10 ? "0" + Math.floor(elapsedMoment.asHours()) : Math.floor(elapsedMoment.asHours());
|
||||
let elapsedString = paddedElapsedMoment + moment.utc(elapsedMs).format(":mm:ss");
|
||||
return "<div class=\"WorkflowChart-elapsedHolder\"><span>" + elapsedString + "</span></div>";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.style("display", function (d) {
|
||||
return (d.job && d.job.elapsed) ? null : "none";
|
||||
});
|
||||
.style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -677,34 +659,76 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
|
||||
let linkEnter = link.enter().append("g")
|
||||
.attr("class", "link")
|
||||
.attr("id", function (d) {
|
||||
return "link-" + d.source.id + "-" + d.target.id;
|
||||
.attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id;});
|
||||
|
||||
linkEnter.append("polygon", "g")
|
||||
.attr("class", function(d) {
|
||||
let linkClasses = ["linkOverlay"];
|
||||
if (d.source.isLinkEditParent && d.target.isLinkEditChild) {
|
||||
linkClasses.push("linkActiveEdit");
|
||||
}
|
||||
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(",");
|
||||
return [pt1, pt2, pt3, pt4].join(" ");
|
||||
})
|
||||
.call(edit_link)
|
||||
.on("mouseover", function(d) {
|
||||
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') {
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id)
|
||||
.classed("overlayHovering", true);
|
||||
}
|
||||
})
|
||||
.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);
|
||||
}
|
||||
});
|
||||
|
||||
// Add entering links in the parent’s old position.
|
||||
linkEnter.insert("path", "g")
|
||||
linkEnter.append("path", "g")
|
||||
.attr("class", function(d) {
|
||||
return (d.source.placeholder || d.target.placeholder) ? "linkPath placeholder" : "linkPath";
|
||||
})
|
||||
.attr("d", lineData)
|
||||
.call(edit_link)
|
||||
.on("mouseover", function(d) {
|
||||
if(!d.source.isStartNode && !d.target.placeholder && scope.mode !== 'details') {
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id)
|
||||
.classed("overlayHovering", true);
|
||||
}
|
||||
})
|
||||
.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);
|
||||
}
|
||||
})
|
||||
.attr('stroke', function(d) {
|
||||
if(d.target.edgeType) {
|
||||
if(d.target.edgeType === "failure") {
|
||||
return "#d9534f";
|
||||
} else if (d.target.edgeType === "success") {
|
||||
}
|
||||
else if(d.target.edgeType === "success") {
|
||||
return "#5cb85c";
|
||||
} else if (d.target.edgeType === "always") {
|
||||
}
|
||||
else if(d.target.edgeType === "always"){
|
||||
return "#337ab7";
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "#D7D7D7";
|
||||
}
|
||||
});
|
||||
|
||||
linkEnter.append("circle")
|
||||
.attr("id", function (d) {
|
||||
return "link-" + d.source.id + "-" + d.target.id + "-add";
|
||||
})
|
||||
.attr("id", function(d){return "link-" + d.source.id + "-" + d.target.id + "-add";})
|
||||
.attr("cx", function(d) {
|
||||
return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
|
||||
})
|
||||
@@ -712,21 +736,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
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 linkCircle")
|
||||
.style("display", function (d) {
|
||||
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
})
|
||||
.attr("class", "addCircle betweenNodesCircle")
|
||||
.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("hovering", true);
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
|
||||
.classed("addHovering", true);
|
||||
})
|
||||
.on("mouseout", function(d){
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id)
|
||||
.classed("hovering", false);
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
|
||||
.classed("addHovering", false);
|
||||
});
|
||||
|
||||
@@ -737,7 +755,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
let translate;
|
||||
if(d.source.isStartNode) {
|
||||
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
|
||||
}
|
||||
return translate;
|
||||
@@ -746,20 +765,14 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
.size(60)
|
||||
.type("cross")
|
||||
)
|
||||
.style("display", function (d) {
|
||||
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
})
|
||||
.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("hovering", true);
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
|
||||
.classed("addHovering", true);
|
||||
})
|
||||
.on("mouseout", function(d){
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id)
|
||||
.classed("hovering", false);
|
||||
d3.select("#link-" + d.source.id + "-" + d.target.id + "-add")
|
||||
.classed("addHovering", false);
|
||||
});
|
||||
|
||||
@@ -769,24 +782,16 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
let t = baseSvg.transition();
|
||||
|
||||
t.selectAll(".nodeCircle")
|
||||
.style("display", function (d) {
|
||||
return d.placeholder || !(userCanAddEdit) ? "none" : null;
|
||||
});
|
||||
.style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; });
|
||||
|
||||
t.selectAll(".nodeAddCross")
|
||||
.style("display", function (d) {
|
||||
return d.placeholder || !(userCanAddEdit) ? "none" : null;
|
||||
});
|
||||
.style("display", function(d) { return d.placeholder || !(userCanAddEdit) ? "none" : null; });
|
||||
|
||||
t.selectAll(".removeCircle")
|
||||
.style("display", function (d) {
|
||||
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
});
|
||||
.style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; });
|
||||
|
||||
t.selectAll(".nodeRemoveCross")
|
||||
.style("display", function (d) {
|
||||
return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
});
|
||||
.style("display", function(d) { return (d.canDelete === false || d.placeholder || !(userCanAddEdit)) ? "none" : null; });
|
||||
|
||||
t.selectAll(".linkPath")
|
||||
.attr("class", function(d) {
|
||||
@@ -797,20 +802,20 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
if(d.target.edgeType) {
|
||||
if(d.target.edgeType === "failure") {
|
||||
return "#d9534f";
|
||||
} else if (d.target.edgeType === "success") {
|
||||
}
|
||||
else if(d.target.edgeType === "success") {
|
||||
return "#5cb85c";
|
||||
} else if (d.target.edgeType === "always") {
|
||||
}
|
||||
else if(d.target.edgeType === "always"){
|
||||
return "#337ab7";
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "#D7D7D7";
|
||||
}
|
||||
});
|
||||
|
||||
t.selectAll(".linkCircle")
|
||||
.style("display", function (d) {
|
||||
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
})
|
||||
t.selectAll(".betweenNodesCircle")
|
||||
.attr("cx", function(d) {
|
||||
return (d.source.isStartNode) ? (d.target.y + d.source.y + rootW) / 2 : (d.target.y + d.source.y + nodeW) / 2;
|
||||
})
|
||||
@@ -818,15 +823,30 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
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(".linkCross")
|
||||
.style("display", function (d) {
|
||||
return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null;
|
||||
t.selectAll(".linkOverlay")
|
||||
.attr("class", function(d) {
|
||||
let linkClasses = ["linkOverlay"];
|
||||
if (d.source.isLinkEditParent && d.target.isLinkEditChild) {
|
||||
linkClasses.push("linkActiveEdit");
|
||||
}
|
||||
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(",");
|
||||
return [pt1, pt2, pt3, pt4].join(" ");
|
||||
});
|
||||
|
||||
t.selectAll(".linkCross")
|
||||
.style("display", function(d) { return (d.source.placeholder || d.target.placeholder || !(userCanAddEdit)) ? "none" : null; })
|
||||
.attr("transform", function(d) {
|
||||
let translate;
|
||||
if(d.source.isStartNode) {
|
||||
translate = "translate(" + (d.target.y + d.source.y + rootW) / 2 + "," + ((d.target.x + startNodeOffsetY + rootH/2) + (d.source.x + nodeH/2)) / 2 + ")";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
translate = "translate(" + (d.target.y + d.source.y + nodeW) / 2 + "," + (d.target.x + d.source.x + nodeH) / 2 + ")";
|
||||
}
|
||||
return translate;
|
||||
@@ -837,12 +857,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
if(d.job && d.job.status) {
|
||||
if(d.job.status === "successful"){
|
||||
return "#5cb85c";
|
||||
} else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
|
||||
}
|
||||
else if (d.job.status === "failed" || d.job.status === "error" || d.job.status === "cancelled") {
|
||||
return "#d9534f";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "#D7D7D7";
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "#D7D7D7";
|
||||
}
|
||||
})
|
||||
@@ -853,14 +876,8 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
});
|
||||
|
||||
t.selectAll(".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 + ")";
|
||||
});
|
||||
.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 + ")"; });
|
||||
|
||||
t.selectAll(".WorkflowChart-nodeTypeCircle")
|
||||
.style("display", function (d) {
|
||||
@@ -945,9 +962,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
|
||||
return statusClass;
|
||||
})
|
||||
.style("display", function (d) {
|
||||
return d.job && d.job.status ? null : "none";
|
||||
})
|
||||
.style("display", function(d) { return d.job && d.job.status ? null : "none"; })
|
||||
.transition()
|
||||
.duration(0)
|
||||
.attr("r", 6)
|
||||
@@ -969,44 +984,26 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
});
|
||||
|
||||
t.selectAll(".WorkflowChart-nameText")
|
||||
.attr("x", function (d) {
|
||||
return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2;
|
||||
})
|
||||
.attr("y", function (d) {
|
||||
return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2;
|
||||
})
|
||||
.attr("text-anchor", function (d) {
|
||||
return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle";
|
||||
})
|
||||
.attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
|
||||
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
|
||||
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
|
||||
.text(function (d) {
|
||||
return (d.unifiedJobTemplate && d.unifiedJobTemplate.name) ? wrap(d.unifiedJobTemplate.name) : "";
|
||||
});
|
||||
|
||||
t.selectAll(".WorkflowChart-detailsLink")
|
||||
.style("display", function (d) {
|
||||
return d.job && d.job.status && d.job.id ? null : "none";
|
||||
});
|
||||
.style("display", function(d){ return d.job && d.job.status && d.job.id ? null : "none"; });
|
||||
|
||||
t.selectAll(".WorkflowChart-deletedText")
|
||||
.style("display", function (d) {
|
||||
return d.unifiedJobTemplate || d.placeholder ? "none" : null;
|
||||
});
|
||||
|
||||
t.selectAll(".WorkflowChart-conflictText")
|
||||
.style("display", function (d) {
|
||||
return (d.edgeConflict && !d.placeholder) ? null : "none";
|
||||
});
|
||||
.style("display", function(d){ return d.unifiedJobTemplate || d.placeholder ? "none" : null; });
|
||||
|
||||
t.selectAll(".WorkflowChart-activeNode")
|
||||
.style("display", function (d) {
|
||||
return d.isActiveEdit ? null : "none";
|
||||
});
|
||||
.style("display", function(d) { return d.isActiveEdit ? null : "none"; });
|
||||
|
||||
t.selectAll(".WorkflowChart-elapsed")
|
||||
.style("display", function (d) {
|
||||
return (d.job && d.job.elapsed) ? null : "none";
|
||||
});
|
||||
} else if (!scope.watchDimensionsSet) {
|
||||
.style("display", function(d) { return (d.job && d.job.elapsed) ? null : "none"; });
|
||||
}
|
||||
else if(!scope.watchDimensionsSet){
|
||||
scope.watchDimensionsSet = scope.$watch('dimensionsSet', function(){
|
||||
if(scope.dimensionsSet) {
|
||||
scope.watchDimensionsSet();
|
||||
@@ -1059,6 +1056,24 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
});
|
||||
}
|
||||
|
||||
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?
|
||||
scope.editLink({
|
||||
parentId: d.source.nodeId,
|
||||
childId: d.target.nodeId
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function link_node() {
|
||||
this.on("click", function(d) {
|
||||
alert('this does not work, don\'t click it');
|
||||
});
|
||||
}
|
||||
|
||||
function details() {
|
||||
this.on("mouseover", function() {
|
||||
d3.select(this).style("text-decoration", "underline");
|
||||
@@ -1070,53 +1085,37 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
|
||||
let goToJobResults = function(job_type) {
|
||||
if(job_type === 'job') {
|
||||
$state.go('output', {
|
||||
id: d.job.id,
|
||||
type: 'playbook'
|
||||
});
|
||||
} else if (job_type === 'inventory_update') {
|
||||
$state.go('output', {
|
||||
id: d.job.id,
|
||||
type: 'inventory'
|
||||
});
|
||||
} else if (job_type === 'project_update') {
|
||||
$state.go('output', {
|
||||
id: d.job.id,
|
||||
type: 'project'
|
||||
});
|
||||
} else if (job_type === 'workflow_job') {
|
||||
$state.go('workflowResults', {
|
||||
id: d.job.id,
|
||||
});
|
||||
$state.go('output', {id: d.job.id, type: 'playbook'});
|
||||
}
|
||||
else if(job_type === 'inventory_update') {
|
||||
$state.go('output', {id: d.job.id, type: 'inventory'});
|
||||
}
|
||||
else if(job_type === 'project_update') {
|
||||
$state.go('output', {id: d.job.id, type: 'project'});
|
||||
}
|
||||
};
|
||||
|
||||
if (d.job.type) {
|
||||
goToJobResults(d.job.type);
|
||||
if(d.job.id) {
|
||||
if(d.unifiedJobTemplate) {
|
||||
goToJobResults(d.unifiedJobTemplate.unified_job_type);
|
||||
}
|
||||
else {
|
||||
// We don't have access to the job type and have to make
|
||||
// We don't have access to the unified resource and have to make
|
||||
// a GET request in order to find out what type job this was
|
||||
// so that we can route the user to the correct stdout view
|
||||
Rest.setUrl(GetBasePath("workflow_jobs") + `${d.originalNodeObj.workflow_job}/workflow_nodes/?order_by=id`);
|
||||
|
||||
Rest.setUrl(GetBasePath("unified_jobs") + "?id=" + d.job.id);
|
||||
Rest.get()
|
||||
.then(function (res) {
|
||||
if(res.data.results && res.data.results.length > 0) {
|
||||
const { results } = res.data;
|
||||
const job = results.filter(result => result.summary_fields.job.id === d.job.id);
|
||||
goToJobResults(job[0].summary_fields.job.type);
|
||||
goToJobResults(res.data.results[0].type);
|
||||
}
|
||||
})
|
||||
.catch(({
|
||||
data,
|
||||
status
|
||||
}) => {
|
||||
ProcessErrors(scope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Unable to get job: ' + status
|
||||
});
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Unable to get job: ' + status });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1159,6 +1158,7 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
function onResize(){
|
||||
let dimensions = calcAvailableScreenSpace();
|
||||
|
||||
$('.WorkflowMaker-chart').css("width", dimensions.width);
|
||||
$('.WorkflowMaker-chart').css("height", dimensions.height);
|
||||
}
|
||||
|
||||
@@ -1177,14 +1177,15 @@ export default ['$state', 'moment', '$timeout', '$window', '$filter', 'Rest', 'G
|
||||
$('.WorkflowMaker-chart').show();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
scope.$on('workflowMakerModalResized', function(){
|
||||
let dimensions = calcAvailableScreenSpace();
|
||||
|
||||
$('.WorkflowMaker-chart').css("width", dimensions.width);
|
||||
$('.WorkflowMaker-chart').css("height", dimensions.height);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
}];
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import workflowLinkForm from './workflow-link-form.directive';
|
||||
import workflowNodeForm from './workflow-node-form.directive';
|
||||
|
||||
export default
|
||||
angular.module('templates.workflowMaker.forms', [])
|
||||
.directive('workflowLinkForm', workflowLinkForm)
|
||||
.directive('workflowNodeForm', workflowNodeForm);
|
||||
@@ -0,0 +1,38 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$scope', 'TemplatesStrings', 'CreateSelect2', '$timeout',
|
||||
function($scope, TemplatesStrings, CreateSelect2, $timeout) {
|
||||
$scope.strings = TemplatesStrings;
|
||||
|
||||
$scope.edgeTypeOptions = [
|
||||
{
|
||||
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'
|
||||
}
|
||||
];
|
||||
|
||||
$scope.$watch('linkConfig.edgeType', () => {
|
||||
if (_.has($scope, 'linkConfig.edgeType')) {
|
||||
$scope.edgeType = {
|
||||
value: $scope.linkConfig.edgeType
|
||||
};
|
||||
CreateSelect2({
|
||||
element: '#workflow_node_edge_2',
|
||||
multiple: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import workflowLinkFormController from './workflow-link-form.controller';
|
||||
|
||||
export default ['templateUrl',
|
||||
function(templateUrl) {
|
||||
return {
|
||||
scope: {
|
||||
linkConfig: '<',
|
||||
cancel: '&',
|
||||
select: '&'
|
||||
},
|
||||
restrict: 'E',
|
||||
templateUrl: templateUrl('templates/workflows/workflow-maker/forms/workflow-link-form'),
|
||||
controller: workflowLinkFormController
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
<div class="WorkflowMaker-formTitle">{{:: strings.get('workflow_maker.EDIT_LINK', {parentName: linkConfig.parent.name, childName: linkConfig.child.name}) }}</div>
|
||||
<div class="WorkflowMaker-form">
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn">
|
||||
<label for="edgeType" class="Form-inputLabelContainer">
|
||||
<span class="Form-requiredAsterisk">*</span>
|
||||
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</span>
|
||||
</label>
|
||||
<div>
|
||||
<select
|
||||
id="workflow_node_edge_2"
|
||||
ng-options="v as v.label for v in edgeTypeOptions track by v.value"
|
||||
ng-model="edgeType"
|
||||
class="form-control Form-dropDown"
|
||||
name="edgeType"
|
||||
tabindex="-1"
|
||||
aria-hidden="true">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons Form-buttons" id="workflow_maker_controls">
|
||||
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_link_btn" ng-click="cancel()"> {{:: strings.get('CANCEL') }}</button>
|
||||
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_link_btn" ng-click="select({parentId: linkConfig.parent.id, childId: linkConfig.child.id, edgeType: edgeType.value})" ng-disabled="!edgeType"> {{:: strings.get('workflow_maker.SELECT') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$scope',
|
||||
function($scope) {
|
||||
console.log('inside wnf controller');
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,21 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2018 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import workflowNodeFormController from './workflow-node-form.controller';
|
||||
|
||||
export default ['templateUrl',
|
||||
function(templateUrl) {
|
||||
return {
|
||||
scope: {},
|
||||
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');
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="Form-tabHolder">
|
||||
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': workflowMakerFormConfig.activeTab === 'jobs'}" ng-click="toggleFormTab('jobs')">{{strings.get('workflow_maker.JOBS')}}</div>
|
||||
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': workflowMakerFormConfig.activeTab === 'project_sync'}" ng-click="toggleFormTab('project_sync')">{{strings.get('workflow_maker.PROJECT_SYNC')}}</div>
|
||||
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': workflowMakerFormConfig.activeTab === 'inventory_sync'}" ng-click="toggleFormTab('inventory_sync')">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</div>
|
||||
</div>
|
||||
<div class="WorkflowMaker-formLists">
|
||||
<div id="workflow-jobs-list" ui-view="jobTemplateList" ng-show="workflowMakerFormConfig.activeTab === 'jobs'"></div>
|
||||
<div id="workflow-project-sync-list" ui-view="projectSyncList" ng-show="workflowMakerFormConfig.activeTab === 'project_sync'"></div>
|
||||
<div id="workflow-inventory-sync-list" ui-view="inventorySyncList" ng-show="workflowMakerFormConfig.activeTab === 'inventory_sync'"></div>
|
||||
</div>
|
||||
<span ng-show="selectedTemplate &&
|
||||
((selectedTemplate.type === 'job_template' && workflowMakerFormConfig.activeTab === 'jobs') ||
|
||||
(selectedTemplate.type === 'project' && workflowMakerFormConfig.activeTab === 'project_sync') ||
|
||||
(selectedTemplate.type === 'inventory_source' && workflowMakerFormConfig.activeTab === 'inventory_sync'))">
|
||||
<div ng-if="selectedTemplate && selectedTemplateInvalid">
|
||||
<div class="WorkflowMaker-invalidJobTemplateWarning">
|
||||
<span class="fa fa-warning"></span>
|
||||
<span>{{:: strings.get('workflows.INVALID_JOB_TEMPLATE') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="selectedTemplate && credentialRequiresPassword">
|
||||
<div class="WorkflowMaker-invalidJobTemplateWarning">
|
||||
<span class="fa fa-warning"></span>
|
||||
<span>{{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch)">
|
||||
<label for="verbosity" class="Form-inputLabelContainer">
|
||||
<span class="Form-requiredAsterisk">*</span>
|
||||
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</span>
|
||||
</label>
|
||||
<div>
|
||||
<select
|
||||
id="workflow_node_edge"
|
||||
ng-options="v as v.label for v in edgeTypeOptions track by v.value"
|
||||
ng-model="edgeType"
|
||||
class="form-control Form-dropDown"
|
||||
name="edgeType"
|
||||
tabindex="-1"
|
||||
ng-disabled="!workflowJobTemplateObj.summary_fields.user_capabilities.edit"
|
||||
aria-hidden="true">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons Form-buttons" id="workflow_maker_controls">
|
||||
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton" ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button>
|
||||
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> {{:: strings.get('CANCEL') }}</button>
|
||||
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_btn" ng-show="!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)" ng-click="cancelNodeForm()"> {{:: strings.get('CLOSE') }}</button>
|
||||
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_btn" ng-show="(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate) && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch)" ng-click="confirmNodeForm()" ng-disabled="!selectedTemplate || promptModalMissingReqFields || credentialRequiresPassword"> {{:: strings.get('workflow_maker.SELECT') }}</button>
|
||||
</div>
|
||||
</span>
|
||||
@@ -1,8 +1,9 @@
|
||||
import workflowMaker from './workflow-maker.directive';
|
||||
import WorkflowMakerController from './workflow-maker.controller';
|
||||
import workflowMakerForms from './forms/main';
|
||||
|
||||
export default
|
||||
angular.module('templates.workflowMaker', [])
|
||||
angular.module('templates.workflowMaker', [workflowMakerForms.name])
|
||||
// In order to test this controller I had to expose it at the module level
|
||||
// like so. Is this correct? Is there a better pattern for doing this?
|
||||
.controller('WorkflowMakerController', WorkflowMakerController)
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
color: @list-title-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.WorkflowMaker-formHelp {
|
||||
|
||||
@@ -203,7 +203,7 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (params.node.edited || !params.node.originalParentId || (params.node.originalParentId && params.parentId !== params.node.originalParentId)) {
|
||||
if (params.node.edited || !params.node.originalParentId || (params.node.originalParentId && (params.parentId !== params.node.originalParentId || params.node.originalEdge !== params.node.edgeType))) {
|
||||
|
||||
if (params.node.edited) {
|
||||
|
||||
@@ -444,6 +444,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
|
||||
$scope.cancelNodeForm();
|
||||
}
|
||||
|
||||
if ($scope.linkBeingEdited) {
|
||||
$scope.cancelLinkForm();
|
||||
}
|
||||
|
||||
$scope.workflowMakerFormConfig.nodeMode = "add";
|
||||
$scope.addParent = parent;
|
||||
$scope.betweenTwoNodes = betweenTwoNodes;
|
||||
@@ -569,6 +573,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
|
||||
|
||||
$scope.startEditNode = function (nodeToEdit) {
|
||||
|
||||
if ($scope.linkBeingEdited) {
|
||||
$scope.cancelLinkForm();
|
||||
}
|
||||
|
||||
if (!$scope.nodeBeingEdited || ($scope.nodeBeingEdited && $scope.nodeBeingEdited.id !== nodeToEdit.id)) {
|
||||
if ($scope.placeholderNode || $scope.nodeBeingEdited) {
|
||||
$scope.cancelNodeForm();
|
||||
@@ -888,6 +896,91 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
|
||||
|
||||
};
|
||||
|
||||
/* EDIT LINK FUNCTIONS */
|
||||
|
||||
$scope.startEditLink = (parentId, childId) => {
|
||||
const setupLinkEdit = () => {
|
||||
const parentNode = WorkflowService.searchTree({
|
||||
element: $scope.treeData.data,
|
||||
matchingId: parentId,
|
||||
byNodeId: true
|
||||
});
|
||||
|
||||
parentNode.isLinkEditParent = true;
|
||||
|
||||
// Loop across children looking for childId
|
||||
const childNode = _.find(parentNode.children, {'nodeId': childId});
|
||||
|
||||
childNode.isLinkEditChild = true;
|
||||
|
||||
$scope.linkBeingEdited = {
|
||||
parent: parentNode,
|
||||
child: childNode
|
||||
}
|
||||
|
||||
$scope.linkConfig = {
|
||||
parent: {
|
||||
id: parentId,
|
||||
name: parentNode.unifiedJobTemplate.name
|
||||
},
|
||||
child: {
|
||||
id: childId,
|
||||
name: childNode.unifiedJobTemplate.name
|
||||
},
|
||||
edgeType: childNode.edgeType
|
||||
}
|
||||
$scope.editLink = true;
|
||||
|
||||
$scope.$broadcast("refreshWorkflowChart");
|
||||
}
|
||||
|
||||
if ($scope.nodeBeingEdited || $scope.placeholderNode) {
|
||||
$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;
|
||||
setupLinkEdit()
|
||||
}
|
||||
} else {
|
||||
setupLinkEdit();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$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.$broadcast("refreshWorkflowChart");
|
||||
}
|
||||
|
||||
$scope.cancelLinkForm = () => {
|
||||
$scope.linkBeingEdited.parent.isLinkEditParent = false;
|
||||
$scope.linkBeingEdited.child.isLinkEditChild = false;
|
||||
$scope.linkBeingEdited = null;
|
||||
|
||||
$scope.editLink = false;
|
||||
|
||||
$scope.$broadcast("refreshWorkflowChart");
|
||||
};
|
||||
|
||||
/* DELETE NODE FUNCTIONS */
|
||||
|
||||
function resetDeleteNode() {
|
||||
@@ -907,6 +1000,10 @@ export default ['$scope', 'WorkflowService', 'TemplatesService',
|
||||
$scope.confirmDeleteNode = function () {
|
||||
if ($scope.nodeToBeDeleted) {
|
||||
|
||||
if ($scope.linkBeingEdited) {
|
||||
$scope.cancelLinkForm();
|
||||
}
|
||||
|
||||
// TODO: turn this into a promise so that we can handle errors
|
||||
|
||||
WorkflowService.removeNodeFromTree({
|
||||
|
||||
@@ -81,9 +81,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<workflow-chart ng-if="modalOpen" tree-data="treeData.data" add-node="startAddNode(parent, betweenTwoNodes)" edit-node="startEditNode(nodeToEdit)" delete-node="startDeleteNode(nodeToDelete)" workflow-zoomed="workflowZoomed(zoom)" can-add-workflow-job-template="canAddWorkflowJobTemplate" workflow-job-template-obj="workflowJobTemplateObj" mode="edit" class="WorkflowMaker-chart"></workflow-chart>
|
||||
<workflow-chart ng-if="modalOpen" tree-data="treeData.data" add-node="startAddNode(parent, betweenTwoNodes)" edit-node="startEditNode(nodeToEdit)" edit-link="startEditLink(parentId, childId)" delete-node="startDeleteNode(nodeToDelete)" workflow-zoomed="workflowZoomed(zoom)" can-add-workflow-job-template="canAddWorkflowJobTemplate" workflow-job-template-obj="workflowJobTemplateObj" mode="edit" class="WorkflowMaker-chart"></workflow-chart>
|
||||
</div>
|
||||
<div class="WorkflowMaker-contentRight">
|
||||
<span ng-show="!editLink">
|
||||
<div class="WorkflowMaker-formTitle">{{(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')}}</div>
|
||||
<div class="WorkflowMaker-formHelp" ng-show="workflowMakerFormConfig.nodeMode === 'idle'" ng-bind="treeData.data.totalNodes === 0 ? strings.get('workflow_maker.PLEASE_CLICK_THE_START_BUTTON') : strings.get('workflow_maker.PLEASE_HOVER_OVER_A_TEMPLATE')"></div>
|
||||
<div class="WorkflowMaker-form" ng-show="workflowMakerFormConfig.nodeMode === 'add' || workflowMakerFormConfig.nodeMode === 'edit'">
|
||||
@@ -98,10 +99,8 @@
|
||||
<div id="workflow-inventory-sync-list" ui-view="inventorySyncList" ng-show="workflowMakerFormConfig.activeTab === 'inventory_sync'"></div>
|
||||
</div>
|
||||
<span ng-show="selectedTemplate &&
|
||||
((selectedTemplate.type === 'job_template' || selectedTemplate.type === 'workflow_job_template' && workflowMakerFormConfig.activeTab === 'jobs') ||
|
||||
(selectedTemplate.unified_job_type === 'job' || selectedTemplate.unified_job_type === 'workflow_job' && workflowMakerFormConfig.activeTab === 'jobs') ||
|
||||
((selectedTemplate.type === 'job_template' && workflowMakerFormConfig.activeTab === 'jobs') ||
|
||||
(selectedTemplate.type === 'project' && workflowMakerFormConfig.activeTab === 'project_sync') ||
|
||||
(selectedTemplate.unified_job_type === 'inventory_update' && workflowMakerFormConfig.activeTab === 'inventory_sync') ||
|
||||
(selectedTemplate.type === 'inventory_source' && workflowMakerFormConfig.activeTab === 'inventory_sync'))">
|
||||
<div ng-if="selectedTemplate && selectedTemplateInvalid">
|
||||
<div class="WorkflowMaker-invalidJobTemplateWarning">
|
||||
@@ -115,7 +114,7 @@
|
||||
<span>{{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch)">
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-show="selectedTemplate && !selectedTemplateInvalid && !(credentialRequiresPassword && !promptData.launchConf.ask_credential_on_launch) && workflowMakerFormConfig.nodeMode === 'add'">
|
||||
<label for="verbosity" class="Form-inputLabelContainer">
|
||||
<span class="Form-requiredAsterisk">*</span>
|
||||
<span class="Form-inputLabel">{{:: strings.get('workflow_maker.RUN') }}</span>
|
||||
@@ -141,6 +140,10 @@
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<span ng-if="editLink">
|
||||
<workflow-link-form link-config="linkConfig" select="confirmLinkForm(parentId, childId, edgeType)" cancel="cancelLinkForm()"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="WorkflowMaker-buttonHolder">
|
||||
|
||||
Reference in New Issue
Block a user