390 lines
11 KiB
JavaScript
390 lines
11 KiB
JavaScript
/**
|
|
* Adapted for Murano js topology generator.
|
|
* Based on:
|
|
* HeatTop JS Framework
|
|
* Dependencies: jQuery 1.7.1 or later, d3 v3 or later
|
|
* Date: June 2013
|
|
* Description: JS Framework that subclasses the D3 Force Directed Graph library to create
|
|
* Heat-specific objects and relationships with the purpose of displaying
|
|
* Stacks, Resources, and related Properties in a Resource Topology Graph.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
* not use this file except in compliance with the License. You may obtain
|
|
* a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
$(function() {
|
|
"use strict";
|
|
horizon.tabs._init_load_functions.push(loadMuranoTopology);
|
|
|
|
function loadMuranoTopology() {
|
|
var muranoContainer = "#murano_application_topology";
|
|
if ($(muranoContainer).length === 0) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* var diagonal = d3.svg.diagonal()
|
|
* .projection(function(d) { return [d.y, d.x]; });
|
|
*/
|
|
|
|
/**
|
|
* If d3 is undefined, and give an assignment
|
|
* It solves no-def error: d3 is not defined
|
|
*/
|
|
//var d3 = d3 || {};
|
|
|
|
/**
|
|
* Declare global variables
|
|
*/
|
|
var ajaxUrl,
|
|
force,
|
|
node,
|
|
link,
|
|
needsUpdate,
|
|
nodes,
|
|
links,
|
|
inProgress;
|
|
|
|
function update() {
|
|
node = node.data(nodes, function(d) {
|
|
return d.id;
|
|
});
|
|
link = link.data(links);
|
|
|
|
var nodeEnter = node.enter().append("g")
|
|
.attr("class", "node")
|
|
.attr("node_name", function(d) {
|
|
return d.name;
|
|
})
|
|
.attr("node_id", function(d) {
|
|
return d.id;
|
|
})
|
|
.call(force.drag);
|
|
|
|
nodeEnter.append("image")
|
|
.attr("xlink:href", function(d) {
|
|
return d.image;
|
|
})
|
|
.attr("id", function(d) {
|
|
return "image_" + d.id;
|
|
})
|
|
.attr("x", function(d) {
|
|
return d.image_x;
|
|
})
|
|
.attr("y", function(d) {
|
|
return d.image_y;
|
|
})
|
|
.attr("width", function(d) {
|
|
return d.image_size;
|
|
})
|
|
.attr("height", function(d) {
|
|
return d.image_size;
|
|
})
|
|
.attr("clip-path", "url(#clipCircle)");
|
|
node.exit().remove();
|
|
|
|
link.enter().insert("path", "g.node")
|
|
.attr("class", function(d) {
|
|
return "link " + d.link_type;
|
|
});
|
|
|
|
link.exit().remove();
|
|
//Setup click action for all nodes
|
|
node.on("mouseover", function(d) {
|
|
$("#info_box").html(d.info_box);
|
|
//current_info = d.name;
|
|
});
|
|
node.on("mouseout", function() {
|
|
$("#info_box").html('');
|
|
});
|
|
|
|
force.start();
|
|
}
|
|
|
|
function drawLink(d) {
|
|
return "M" + d.source.x + "," + d.source.y + "L" + d.target.x + "," + d.target.y;
|
|
}
|
|
|
|
function tick() {
|
|
link.attr('d', drawLink).style('stroke-width', 3).attr('marker-end', "url(#end)");
|
|
node.attr("transform", function(d) {
|
|
return "translate(" + d.x + "," + d.y + ")";
|
|
});
|
|
}
|
|
|
|
function setInProgress(stack, innerNodes) {
|
|
if (stack.in_progress === true) {
|
|
inProgress = true;
|
|
}
|
|
for (var i = 0; i < innerNodes.length; i++) {
|
|
var d = innerNodes[i];
|
|
if (d.in_progress === true) {
|
|
inProgress = true; return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function findNode(id) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (nodes[i].id === id) {
|
|
return nodes[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
function findNodeIndex(id) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (nodes[i].id === id) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
function addNode(innerNode) {
|
|
nodes.push(innerNode);
|
|
needsUpdate = true;
|
|
}
|
|
|
|
function removeNode(id) {
|
|
var i = 0;
|
|
var n = findNode(id);
|
|
while (i < links.length) {
|
|
if (links[i].source === n || links[i].target === n) {
|
|
links.splice(i, 1);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
nodes.splice(findNodeIndex(id), 1);
|
|
needsUpdate = true;
|
|
}
|
|
|
|
function removeNodes(oldNodes, newNodes) {
|
|
//Check for removed nodes
|
|
for (var i = 0; i < oldNodes.length; i++) {
|
|
var isRemoveNode = true;
|
|
for (var j = 0; j < newNodes.length; j++) {
|
|
if (oldNodes[i].id === newNodes[j].id) {
|
|
isRemoveNode = false;
|
|
break;
|
|
}
|
|
}
|
|
if (isRemoveNode === true) {
|
|
removeNode(oldNodes[i].id);
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildNodeLinks(innerNode) {
|
|
for (var j = 0; j < innerNode.required_by.length; j++) {
|
|
var pushLink = true;
|
|
var targetIdx = '';
|
|
var sourceIdx = findNodeIndex(innerNode.id);
|
|
//make sure target node exists
|
|
try {
|
|
targetIdx = findNodeIndex(innerNode.required_by[j]);
|
|
} catch (err) {
|
|
if (window.console) {
|
|
window.console.log(err);
|
|
}
|
|
pushLink = false;
|
|
}
|
|
//check for duplicates
|
|
for (var lidx = 0; lidx < links.length; lidx++) {
|
|
if (links[lidx].source === sourceIdx && links[lidx].target === targetIdx) {
|
|
pushLink = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pushLink === true && (sourceIdx && targetIdx)) {
|
|
links.push({
|
|
"target": sourceIdx,
|
|
"source": targetIdx,
|
|
"value": 1,
|
|
"link_type": innerNode.link_type
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildReverseLinks(innerNode) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (nodes[i].required_by) {
|
|
for (var j = 0; j < nodes[i].required_by.length; j++) {
|
|
var dependency = nodes[i].required_by[j];
|
|
//if new node is required by existing node, push new link
|
|
if (innerNode.id === dependency) {
|
|
links.push({
|
|
"target": findNodeIndex(nodes[i].id),
|
|
"source": findNodeIndex(innerNode.id),
|
|
"value": 1,
|
|
"link_type": nodes[i].link_type
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildLinks() {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
buildNodeLinks(nodes[i]);
|
|
buildReverseLinks(nodes[i]);
|
|
}
|
|
}
|
|
|
|
function ajaxPoll(pollTime) {
|
|
setTimeout(function() {
|
|
$.getJSON(ajaxUrl, function(json) {
|
|
//update d3 data element
|
|
$("#d3_data").attr("data-d3_data", JSON.stringify(json));
|
|
|
|
//update stack
|
|
$("#stack_box").html(json.environment.info_box);
|
|
setInProgress(json.environment, json.nodes);
|
|
needsUpdate = false;
|
|
|
|
//Check Remove nodes
|
|
removeNodes(nodes, json.nodes);
|
|
|
|
//Check for updates and new nodes
|
|
json.nodes.forEach(function(d) {
|
|
var currentNode = findNode(d.id);
|
|
//Check if node already exists
|
|
if (currentNode) {
|
|
//Node already exists, just update it
|
|
currentNode.status = d.status;
|
|
|
|
//Status has changed, image should be updated
|
|
if (currentNode.image !== d.image) {
|
|
currentNode.image = d.image;
|
|
var thisImage = d3.select("#image_" + currentNode.id);
|
|
thisImage
|
|
.transition()
|
|
.attr("x", function(dImage) {
|
|
return dImage.image_x + 5;
|
|
})
|
|
.duration(100)
|
|
.transition()
|
|
.attr("x", function(dImage) {
|
|
return dImage.image_x - 5;
|
|
})
|
|
.duration(100)
|
|
.transition()
|
|
.attr("x", function(dImage) {
|
|
return dImage.image_x + 5;
|
|
})
|
|
.duration(100)
|
|
.transition()
|
|
.attr("x", function(dImage) {
|
|
return dImage.image_x - 5;
|
|
})
|
|
.duration(100)
|
|
.transition()
|
|
.attr("xlink:href", d.image)
|
|
.transition()
|
|
.attr("x", function(dImage) {
|
|
return dImage.image_x;
|
|
})
|
|
.duration(100)
|
|
.ease("bounce");
|
|
}
|
|
|
|
//Status has changed, update info_box
|
|
currentNode.info_box = d.info_box;
|
|
|
|
} else {
|
|
addNode(d);
|
|
buildLinks();
|
|
}
|
|
});
|
|
|
|
//if any updates needed, do update now
|
|
if (needsUpdate === true) {
|
|
update();
|
|
}
|
|
});
|
|
//if no nodes still in progress, slow AJAX polling
|
|
if (inProgress === false) {
|
|
pollTime = 30000;
|
|
} else {
|
|
pollTime = 3000;
|
|
}
|
|
ajaxPoll(pollTime);
|
|
}, pollTime);
|
|
}
|
|
|
|
if ($(muranoContainer).length) {
|
|
var width = $(muranoContainer).width();
|
|
var height = 1040;
|
|
var environmentId = $("#environment_id").data("environment_id");
|
|
var graph = $("#d3_data").data("d3_data");
|
|
var svg = d3.select(muranoContainer).append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height);
|
|
|
|
ajaxUrl = '/app-catalog/' + environmentId + '/services/get_d3_data';
|
|
force = d3.layout.force()
|
|
.nodes(graph.nodes)
|
|
.links([])
|
|
.gravity(0.25)
|
|
.charge(-3000)
|
|
.linkDistance(100)
|
|
.size([width, height])
|
|
.on("tick", tick);
|
|
node = svg.selectAll(".node");
|
|
link = svg.selectAll(".link");
|
|
needsUpdate = false;
|
|
nodes = force.nodes();
|
|
links = force.links();
|
|
svg.append("svg:clipPath")
|
|
.attr("id", "clipCircle")
|
|
.append("svg:circle")
|
|
.attr("cursor", "pointer")
|
|
.attr("r", "38px");
|
|
|
|
svg.append("svg:defs").selectAll("marker")
|
|
.data(["end"]) // Different link/path types can be defined here
|
|
.enter().append("svg:marker") // This section adds in the arrows
|
|
.attr("id", String)
|
|
.attr("viewBox", "0 -5 10 10")
|
|
.attr("refX", 25)
|
|
.attr("refY", 0)
|
|
.attr("fill", "#999")
|
|
.attr("markerWidth", 6)
|
|
.attr("markerHeight", 6)
|
|
.attr("orient", "auto")
|
|
.append("svg:path")
|
|
.attr("d", "M0,-3L10,0L0,3");
|
|
|
|
buildLinks();
|
|
update();
|
|
|
|
//Load initial Stack box
|
|
$("#stack_box").html(graph.environment.info_box);
|
|
//On Page load, set Action In Progress
|
|
inProgress = false;
|
|
setInProgress(graph.environment, node);
|
|
|
|
//If status is In Progress, start AJAX polling
|
|
var pollTime = 0;
|
|
if (inProgress === true) {
|
|
pollTime = 3000;
|
|
} else {
|
|
pollTime = 30000;
|
|
}
|
|
ajaxPoll(pollTime);
|
|
}
|
|
}
|
|
});
|