295175826b
hozoin doc says that the horizon directory holds the generic libraries and components that can be used in any Django project. So, this patch move js files related with Openstack components into openstack_dashboard folder. Change-Id: Ib4ed05e1136ee3f70a201139e456d4c3b93519f3 Closes-bug: #1635519
1091 lines
34 KiB
JavaScript
1091 lines
34 KiB
JavaScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
/* global Hogan */
|
|
/* Namespace for core functionality related to Network Topology. */
|
|
|
|
function Network(data) {
|
|
for (var key in data) {
|
|
if ({}.hasOwnProperty.call(data, key)) {
|
|
this[key] = data[key];
|
|
}
|
|
}
|
|
this.iconType = 'text';
|
|
this.icon = '\uf0c2'; // Cloud
|
|
this.collapsed = false;
|
|
this.type = 'network';
|
|
this.instances = 0;
|
|
}
|
|
|
|
function ExternalNetwork(data) {
|
|
for (var key in data) {
|
|
if ({}.hasOwnProperty.call(data, key)) {
|
|
this[key] = data[key];
|
|
}
|
|
}
|
|
this.collapsed = false;
|
|
this.iconType = 'text';
|
|
this.icon = '\uf0ac'; // Globe
|
|
this.instances = 0;
|
|
}
|
|
|
|
function Router(data) {
|
|
for (var key in data) {
|
|
if ({}.hasOwnProperty.call(data, key)) {
|
|
this[key] = data[key];
|
|
}
|
|
}
|
|
this.iconType = 'path';
|
|
this.svg = 'router';
|
|
this.networks = [];
|
|
this.ports = [];
|
|
this.type = 'router';
|
|
}
|
|
|
|
function Server(data) {
|
|
for (var key in data) {
|
|
if ({}.hasOwnProperty.call(data, key)) {
|
|
this[key] = data[key];
|
|
}
|
|
}
|
|
this.iconType = 'text';
|
|
this.icon = '\uf108'; // Server
|
|
this.networks = [];
|
|
this.type = 'instance';
|
|
this.ip_addresses = [];
|
|
}
|
|
|
|
function listContains(obj, list) {
|
|
// Function to help checking if an object is present on a list
|
|
for (var i = 0; i < list.length; i++) {
|
|
if (angular.equals(list[i], obj)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
horizon.network_topology = {
|
|
fa_globe_glyph: '\uf0ac',
|
|
fa_globe_glyph_width: 15,
|
|
svg:'#topology_canvas',
|
|
nodes: [],
|
|
links: [],
|
|
data: [],
|
|
zoom: d3.behavior.zoom(),
|
|
data_loaded: false,
|
|
svg_container:'#topologyCanvasContainer',
|
|
balloonTmpl : null,
|
|
balloon_deviceTmpl : null,
|
|
balloon_portTmpl : null,
|
|
balloon_netTmpl : null,
|
|
balloon_instanceTmpl : null,
|
|
network_index: {},
|
|
balloonID:null,
|
|
network_height : 0,
|
|
|
|
init:function() {
|
|
var self = this;
|
|
angular.element(self.svg_container).spin(horizon.conf.spinner_options.modal);
|
|
if (angular.element('#networktopology').length === 0) {
|
|
return;
|
|
}
|
|
|
|
self.data = {};
|
|
self.data.networks = {};
|
|
self.data.routers = {};
|
|
self.data.servers = {};
|
|
self.data.ports = {};
|
|
|
|
// Setup balloon popups
|
|
self.balloonTmpl = Hogan.compile(angular.element('#balloon_container').html());
|
|
self.balloon_deviceTmpl = Hogan.compile(angular.element('#balloon_device').html());
|
|
self.balloon_portTmpl = Hogan.compile(angular.element('#balloon_port').html());
|
|
self.balloon_netTmpl = Hogan.compile(angular.element('#balloon_net').html());
|
|
self.balloon_instanceTmpl = Hogan.compile(angular.element('#balloon_instance').html());
|
|
|
|
angular.element(document)
|
|
.on('click', 'a.closeTopologyBalloon', function(e) {
|
|
e.preventDefault();
|
|
self.delete_balloon();
|
|
})
|
|
.on('click', '.topologyBalloon', function(e) {
|
|
e.stopPropagation();
|
|
})
|
|
.on('click', 'a.vnc_window', function(e) {
|
|
e.preventDefault();
|
|
var vncWindow = window.open(angular.element(this).attr('href'), vncWindow,
|
|
'width=760,height=560');
|
|
self.delete_balloon();
|
|
});
|
|
|
|
angular.element('#toggle_labels').click(function() {
|
|
if (angular.element('.nodeLabel').css('display') == 'none') {
|
|
angular.element('.nodeLabel').show();
|
|
horizon.cookies.put('show_labels', true);
|
|
} else {
|
|
angular.element('.nodeLabel').hide();
|
|
horizon.cookies.put('show_labels', false);
|
|
}
|
|
});
|
|
|
|
angular.element('#toggle_networks').click(function() {
|
|
for (var n in self.nodes) {
|
|
if ({}.hasOwnProperty.call(self.nodes, n)) {
|
|
if (self.nodes[n].data instanceof Network || self.nodes[n].data instanceof ExternalNetwork) {
|
|
self.collapse_network(self.nodes[n]);
|
|
}
|
|
if (horizon.cookies.get('show_labels')) {
|
|
angular.element('.nodeLabel').show();
|
|
}
|
|
}
|
|
}
|
|
var current = horizon.cookies.get('are_networks_collapsed');
|
|
horizon.cookies.put('are_networks_collapsed', !current);
|
|
});
|
|
|
|
angular.element('#topologyCanvasContainer').spin(horizon.conf.spinner_options.modal);
|
|
self.create_vis();
|
|
self.loading();
|
|
self.force_direction(0.05,70,-700);
|
|
if(horizon.networktopologyloader.model !== null) {
|
|
self.retrieve_network_info(true);
|
|
}
|
|
|
|
d3.select(window).on('resize', function() {
|
|
var width = angular.element('#topologyCanvasContainer').width();
|
|
var height = angular.element('#topologyCanvasContainer').height();
|
|
self.force.size([width, height]).resume();
|
|
});
|
|
|
|
angular.element('#networktopology').on('change', function() {
|
|
self.retrieve_network_info(true);
|
|
});
|
|
|
|
// register for message notifications
|
|
horizon.networktopologymessager.addMessageHandler(this.handleMessage, this);
|
|
},
|
|
|
|
handleMessage:function(message) {
|
|
var self = this;
|
|
var deleteData = horizon.networktopologymessager.delete_data;
|
|
if (message.type == 'success') {
|
|
self.remove_node_on_delete(deleteData);
|
|
}
|
|
},
|
|
|
|
// Get the json data about the current deployment
|
|
retrieve_network_info: function(force_start) {
|
|
var self = this;
|
|
self.data_loaded = true;
|
|
self.load_topology(horizon.networktopologyloader.model);
|
|
if (force_start) {
|
|
var i = 0;
|
|
self.force.start();
|
|
while (i <= 100) {
|
|
self.force.tick();
|
|
i++;
|
|
}
|
|
}
|
|
},
|
|
|
|
// Load config from cookie
|
|
load_config: function() {
|
|
var labels = horizon.cookies.get('show_labels');
|
|
var networks = horizon.cookies.get('are_networks_collapsed');
|
|
if (labels) {
|
|
angular.element('.nodeLabel').show();
|
|
angular.element('#toggle_labels').addClass('active');
|
|
}
|
|
if (networks) {
|
|
for (var n in this.nodes) {
|
|
if ({}.hasOwnProperty.call(this.nodes, n)) {
|
|
this.collapse_network(this.nodes[n], true);
|
|
}
|
|
}
|
|
angular.element('#toggle_networks').addClass('active');
|
|
}
|
|
},
|
|
|
|
getScreenCoords: function(x, y) {
|
|
var self = this;
|
|
if (self.translate) {
|
|
var xn = self.translate[0] + x * self.zoom.scale();
|
|
var yn = self.translate[1] + y * self.zoom.scale();
|
|
return { x: xn, y: yn };
|
|
} else {
|
|
return { x: x, y: y };
|
|
}
|
|
},
|
|
|
|
// Setup the main visualisation
|
|
create_vis: function() {
|
|
var self = this;
|
|
angular.element('#topologyCanvasContainer').html('');
|
|
|
|
// Main svg
|
|
self.outer_group = d3.select('#topologyCanvasContainer').append('svg')
|
|
.attr('width', '100%')
|
|
.attr('height', angular.element(document).height() - 270 + "px")
|
|
.attr('pointer-events', 'all')
|
|
.append('g')
|
|
.call(self.zoom
|
|
.scaleExtent([0.1,1.5])
|
|
.on('zoom', function() {
|
|
self.delete_balloon();
|
|
self.vis.attr('transform', 'translate(' + d3.event.translate + ')scale(' +
|
|
self.zoom.scale() + ')');
|
|
self.translate = d3.event.translate;
|
|
})
|
|
)
|
|
.on('dblclick.zoom', null);
|
|
|
|
// Background for capturing mouse events
|
|
self.outer_group.append('rect')
|
|
.attr('width', '100%')
|
|
.attr('height', '100%')
|
|
.attr('fill', 'white')
|
|
.on('click', function() {
|
|
self.delete_balloon();
|
|
});
|
|
|
|
// svg wrapper for nodes to sit on
|
|
self.vis = self.outer_group.append('g');
|
|
},
|
|
|
|
loading: function() {
|
|
var self = this;
|
|
var load_text = self.vis.append('text')
|
|
.style('fill', 'black')
|
|
.style('font-size', '40')
|
|
.attr('x', '50%')
|
|
.attr('y', '50%')
|
|
.text('');
|
|
var counter = 0;
|
|
var timer = setInterval(function() {
|
|
var i;
|
|
var str = '';
|
|
for (i = 0; i <= counter; i++) {
|
|
str += '.';
|
|
}
|
|
load_text.text(str);
|
|
if (counter >= 9) {
|
|
counter = 0;
|
|
} else {
|
|
counter++;
|
|
}
|
|
if (self.data_loaded) {
|
|
clearInterval(timer);
|
|
load_text.remove();
|
|
}
|
|
}, 100);
|
|
},
|
|
|
|
// Calculate the hulls that surround networks
|
|
convex_hulls: function(nodes) {
|
|
var net, _i, _len, _ref, _h, i;
|
|
var hulls = {};
|
|
var networkids = {};
|
|
var k = 0;
|
|
var offset = 40;
|
|
|
|
while (k < nodes.length) {
|
|
var n = nodes[k];
|
|
if (n.data !== undefined) {
|
|
if (n.data instanceof Server) {
|
|
_ref = n.data.networks;
|
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
net = _ref[_i];
|
|
if (net instanceof Network) {
|
|
_h = hulls[net.id] || (hulls[net.id] = []);
|
|
_h.push([n.x - offset, n.y - offset]);
|
|
_h.push([n.x - offset, n.y + offset]);
|
|
_h.push([n.x + offset, n.y - offset]);
|
|
_h.push([n.x + offset, n.y + offset]);
|
|
}
|
|
}
|
|
} else if (n.data instanceof Network) {
|
|
net = n.data;
|
|
networkids[net.id] = n;
|
|
_h = hulls[net.id] || (hulls[net.id] = []);
|
|
_h.push([n.x - offset, n.y - offset]);
|
|
_h.push([n.x - offset, n.y + offset]);
|
|
_h.push([n.x + offset, n.y - offset]);
|
|
_h.push([n.x + offset, n.y + offset]);
|
|
|
|
}
|
|
}
|
|
++k;
|
|
}
|
|
var hullset = [];
|
|
for (i in hulls) {
|
|
if ({}.hasOwnProperty.call(hulls, i)) {
|
|
hullset.push({group: i, network: networkids[i], path: d3.geom.hull(hulls[i])});
|
|
}
|
|
}
|
|
|
|
return hullset;
|
|
},
|
|
|
|
// Setup the force direction
|
|
force_direction: function(grav, dist, ch) {
|
|
var self = this;
|
|
|
|
angular.element('[data-toggle="tooltip"]').tooltip({container: 'body'});
|
|
self.curve = d3.svg.line()
|
|
.interpolate('cardinal-closed')
|
|
.tension(0.85);
|
|
self.fill = d3.scale.category10();
|
|
|
|
self.force = d3.layout.force()
|
|
.gravity(grav)
|
|
.linkDistance(function(d) {
|
|
if (d.source.data instanceof Server || d.target.data instanceof Server) {
|
|
if (d.source.data.networks) {
|
|
return (dist * d.source.data.networks.length) + (5 * d.target.data.instances) + 20;
|
|
} else if (d.target.data.networks) {
|
|
return (dist * d.target.data.networks.length) + (5 * d.source.data.instances) + 20;
|
|
}
|
|
} else if (d.source.data instanceof Router || d.target.data instanceof Router) {
|
|
if (d.source.data.networks) {
|
|
if (d.source.data.networks.length === 0) {
|
|
return dist + 20;
|
|
} else if (d.target.data.instances) {
|
|
return dist * d.source.data.networks.length + (10 * d.target.data.instances) + 20;
|
|
}
|
|
return dist * d.source.data.networks.length + 20;
|
|
} else if (d.target.data.networks) {
|
|
if (d.target.data.networks.length === 0) {
|
|
return dist + 20;
|
|
} else if (d.source.data.instances) {
|
|
return dist * d.target.data.networks.length + (10 * d.source.data.instances) + 20;
|
|
}
|
|
return dist * d.source.data.networks.length + 20;
|
|
}
|
|
} else {
|
|
return dist;
|
|
}
|
|
})
|
|
.charge(ch)
|
|
.size([angular.element('#topologyCanvasContainer').width(),
|
|
angular.element('#topologyCanvasContainer').height()])
|
|
.nodes(self.nodes)
|
|
.links(self.links)
|
|
.on('tick', function() {
|
|
self.vis.selectAll('g.node')
|
|
.attr('transform', function(d) {
|
|
return 'translate(' + d.x + ',' + d.y + ')';
|
|
});
|
|
|
|
self.vis.selectAll('line.link')
|
|
.attr('x1', function(d) { return d.source.x; })
|
|
.attr('y1', function(d) { return d.source.y; })
|
|
.attr('x2', function(d) { return d.target.x; })
|
|
.attr('y2', function(d) { return d.target.y; });
|
|
|
|
self.vis.selectAll('path.hulls')
|
|
.data(self.convex_hulls(self.vis.selectAll('g.node').data()))
|
|
.attr('d', function(d) {
|
|
return self.curve(d.path);
|
|
})
|
|
.enter().insert('path', 'g')
|
|
.attr('class', 'hulls')
|
|
.style('fill', function(d) {
|
|
return self.fill(d.group);
|
|
})
|
|
.style('stroke', function(d) {
|
|
return self.fill(d.group);
|
|
})
|
|
.style('stroke-linejoin', 'round')
|
|
.style('stroke-width', 10)
|
|
.style('opacity', 0.2);
|
|
});
|
|
},
|
|
|
|
// Create a new node
|
|
new_node: function(data, x, y) {
|
|
var self = this;
|
|
data = {data: data};
|
|
if (x && y) {
|
|
data.x = x;
|
|
data.y = y;
|
|
}
|
|
self.nodes.push(data);
|
|
|
|
var node = self.vis.selectAll('g.node').data(self.nodes);
|
|
var nodeEnter = node.enter().append('g')
|
|
.attr('class', 'node')
|
|
.style('fill', 'white')
|
|
.call(self.force.drag);
|
|
|
|
nodeEnter.append('circle')
|
|
.attr('class', 'frame')
|
|
.attr('r', function(d) {
|
|
switch (Object.getPrototypeOf(d.data)) {
|
|
case ExternalNetwork.prototype:
|
|
return 35;
|
|
case Network.prototype:
|
|
return 30;
|
|
case Router.prototype:
|
|
return 25;
|
|
case Server.prototype:
|
|
return 20;
|
|
}
|
|
})
|
|
.style('fill', 'white')
|
|
.style('stroke', 'black')
|
|
.style('stroke-width', 3);
|
|
|
|
switch (data.data.iconType) {
|
|
case 'text':
|
|
nodeEnter.append('text')
|
|
.style('fill', 'black')
|
|
.style('font', '20px FontAwesome')
|
|
.attr('text-anchor', 'middle')
|
|
.attr('dominant-baseline', 'central')
|
|
.text(function(d) { return d.data.icon; })
|
|
.attr('transform', function(d) {
|
|
switch (Object.getPrototypeOf(d.data)) {
|
|
case ExternalNetwork.prototype:
|
|
return 'scale(2.5)';
|
|
case Network.prototype:
|
|
return 'scale(1.5)';
|
|
case Server.prototype:
|
|
return 'scale(1)';
|
|
}
|
|
});
|
|
break;
|
|
case 'path':
|
|
nodeEnter.append('path')
|
|
.attr('class', 'svgpath')
|
|
.style('fill', 'black')
|
|
.attr('d', function(d) { return self.svgs(d.data.svg); })
|
|
.attr('transform', function() {
|
|
return 'scale(1.2)translate(-16,-15)';
|
|
});
|
|
break;
|
|
}
|
|
|
|
nodeEnter.append('text')
|
|
.attr('class', 'nodeLabel')
|
|
.style('display',function() {
|
|
var labels = horizon.cookies.get('topology_labels');
|
|
if (labels) {
|
|
return 'inline';
|
|
} else {
|
|
return 'none';
|
|
}
|
|
})
|
|
.style('fill','black')
|
|
.text(function(d) {
|
|
return d.data.name;
|
|
})
|
|
.attr('transform', function(d) {
|
|
switch (Object.getPrototypeOf(d.data)) {
|
|
case ExternalNetwork.prototype:
|
|
return 'translate(40,3)';
|
|
case Network.prototype:
|
|
return 'translate(35,3)';
|
|
case Router.prototype:
|
|
return 'translate(30,3)';
|
|
case Server.prototype:
|
|
return 'translate(25,3)';
|
|
}
|
|
});
|
|
|
|
if (data.data instanceof Network || data.data instanceof ExternalNetwork) {
|
|
nodeEnter.append('svg:text')
|
|
.attr('class','vmCount')
|
|
.style('fill', 'black')
|
|
.style('font-size','20')
|
|
.text('')
|
|
.attr('transform', 'translate(26,38)');
|
|
}
|
|
|
|
nodeEnter.on('click', function(d) {
|
|
self.show_balloon(d.data, d, angular.element(this));
|
|
});
|
|
|
|
// Highlight the links for currently selected node
|
|
nodeEnter.on('mouseover', function(d) {
|
|
self.vis.selectAll('line.link').filter(function(z) {
|
|
if (z.source === d || z.target === d) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}).style('stroke-width', '3px');
|
|
});
|
|
|
|
// Remove the highlight on the links
|
|
nodeEnter.on('mouseout', function() {
|
|
self.vis.selectAll('line.link').style('stroke-width','1px');
|
|
});
|
|
},
|
|
|
|
collapse_network: function(d, only_collapse) {
|
|
var self = this;
|
|
var server, vm;
|
|
|
|
var filterNode = function(obj) {
|
|
return function(d) {
|
|
return obj == d.data;
|
|
};
|
|
};
|
|
|
|
if (!d.data.collapsed) {
|
|
var vmCount = 0;
|
|
for (vm in self.data.servers) {
|
|
if (self.data.servers[vm] !== undefined) {
|
|
if (self.data.servers[vm].networks.length == 1) {
|
|
if (self.data.servers[vm].networks[0].id == d.data.id) {
|
|
vmCount += 1;
|
|
self.removeNode(self.data.servers[vm]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
d.data.collapsed = true;
|
|
if (vmCount > 0) {
|
|
self.vis.selectAll('.vmCount').filter(filterNode(d.data))[0][0].textContent = vmCount;
|
|
}
|
|
} else if (!only_collapse) {
|
|
for (server in self.data.servers) {
|
|
if ({}.hasOwnProperty.call(self.data.servers, server)) {
|
|
var _vm = self.data.servers[server];
|
|
if (_vm !== undefined) {
|
|
if (_vm.networks.length === 1) {
|
|
if (_vm.networks[0].id == d.data.id) {
|
|
self.new_node(_vm, d.x, d.y);
|
|
self.new_link(self.find_by_id(_vm.id), self.find_by_id(d.data.id));
|
|
self.force.start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
d.data.collapsed = false;
|
|
self.vis.selectAll('.vmCount').filter(filterNode(d.data))[0][0].textContent = '';
|
|
var i = 0;
|
|
while (i <= 100) {
|
|
self.force.tick();
|
|
i++;
|
|
}
|
|
}
|
|
},
|
|
|
|
new_link: function(source, target) {
|
|
var self = this;
|
|
self.links.push({source: source, target: target});
|
|
var line = self.vis.selectAll('line.link').data(self.links);
|
|
line.enter().insert('line', 'g.node')
|
|
.attr('class', 'link')
|
|
.attr('x1', function(d) { return d.source.x; })
|
|
.attr('y1', function(d) { return d.source.y; })
|
|
.attr('x2', function(d) { return d.target.x; })
|
|
.attr('y2', function(d) { return d.target.y; })
|
|
.style('stroke', 'black')
|
|
.style('stroke-width', 2);
|
|
},
|
|
|
|
find_by_id: function(id) {
|
|
var self = this;
|
|
var obj, _i, _len, _ref;
|
|
_ref = self.vis.selectAll('g.node').data();
|
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
obj = _ref[_i];
|
|
if (obj.data.id == id) {
|
|
return obj;
|
|
}
|
|
}
|
|
return undefined;
|
|
},
|
|
|
|
already_in_graph: function(data, node) {
|
|
// Check for gateway that may not have unique id
|
|
if (data == this.data.ports) {
|
|
for (var p in data) {
|
|
if (JSON.stringify(data[p]) == JSON.stringify(node)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// All other node types have UUIDs
|
|
for (var n in data) {
|
|
if (n == node.id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
load_topology: function(data) {
|
|
var self = this;
|
|
var net, _i, _netlen, _netref, rou, _j, _roulen, _rouref, port, _l, _portlen, _portref,
|
|
ser, _k, _serlen, _serref, obj, vmCount;
|
|
var change = false;
|
|
var filterNode = function(obj) {
|
|
return function(d) {
|
|
return obj == d.data;
|
|
};
|
|
};
|
|
|
|
// Networks
|
|
_netref = data.networks;
|
|
for (_i = 0, _netlen = _netref.length; _i < _netlen; _i++) {
|
|
net = _netref[_i];
|
|
var network = null;
|
|
if (net['router:external'] === true) {
|
|
network = new ExternalNetwork(net);
|
|
} else {
|
|
network = new Network(net);
|
|
}
|
|
|
|
if (!self.already_in_graph(self.data.networks, network)) {
|
|
self.new_node(network);
|
|
change = true;
|
|
} else {
|
|
obj = self.find_by_id(network.id);
|
|
if (obj) {
|
|
network.collapsed = obj.data.collapsed;
|
|
network.instances = obj.data.instances;
|
|
obj.data = network;
|
|
}
|
|
}
|
|
self.data.networks[network.id] = network;
|
|
}
|
|
|
|
// Routers
|
|
_rouref = data.routers;
|
|
for (_j = 0, _roulen = _rouref.length; _j < _roulen; _j++) {
|
|
rou = _rouref[_j];
|
|
var router = new Router(rou);
|
|
if (!self.already_in_graph(self.data.routers, router)) {
|
|
self.new_node(router);
|
|
change = true;
|
|
} else {
|
|
obj = self.find_by_id(router.id);
|
|
if (obj) {
|
|
// Keep networks list
|
|
router.networks = obj.data.networks;
|
|
// Keep ports list
|
|
router.ports = obj.data.ports;
|
|
obj.data = router;
|
|
}
|
|
}
|
|
self.data.routers[router.id] = router;
|
|
}
|
|
|
|
// Servers
|
|
_serref = data.servers;
|
|
for (_k = 0, _serlen = _serref.length; _k < _serlen; _k++) {
|
|
ser = _serref[_k];
|
|
var server = new Server(ser);
|
|
if (!self.already_in_graph(self.data.servers, server)) {
|
|
self.new_node(server);
|
|
change = true;
|
|
} else {
|
|
obj = self.find_by_id(server.id);
|
|
if (obj) {
|
|
// Keep networks list
|
|
server.networks = obj.data.networks;
|
|
// Keep ip address list
|
|
server.ip_addresses = obj.data.ip_addresses;
|
|
obj.data = server;
|
|
} else if (self.data.servers[server.id] !== undefined) {
|
|
// This is used when servers are hidden because the network is
|
|
// collapsed
|
|
server.networks = self.data.servers[server.id].networks;
|
|
server.ip_addresses = self.data.servers[server.id].ip_addresses;
|
|
}
|
|
}
|
|
self.data.servers[server.id] = server;
|
|
}
|
|
|
|
// Ports
|
|
_portref = data.ports;
|
|
for (_l = 0, _portlen = _portref.length; _l < _portlen; _l++) {
|
|
port = _portref[_l];
|
|
if (!self.already_in_graph(self.data.ports, port)) {
|
|
var device = self.find_by_id(port.device_id);
|
|
var _network = self.find_by_id(port.network_id);
|
|
if (angular.isDefined(device) && angular.isDefined(_network)) {
|
|
if (port.device_owner == 'compute:nova' || port.device_owner == 'compute:None') {
|
|
_network.data.instances++;
|
|
device.data.networks.push(_network.data);
|
|
if (port.fixed_ips) {
|
|
for(var ip in port.fixed_ips) {
|
|
if (!listContains(port.fixed_ips[ip], device.data.ip_addresses)) {
|
|
device.data.ip_addresses.push(port.fixed_ips[ip]);
|
|
}
|
|
}
|
|
}
|
|
// Remove the recently added node if connected to a network which is
|
|
// currently collapsed
|
|
if (_network.data.collapsed) {
|
|
if (device.data.networks.length == 1) {
|
|
self.data.servers[device.data.id].networks = device.data.networks;
|
|
self.data.servers[device.data.id].ip_addresses = device.data.ip_addresses;
|
|
self.removeNode(self.data.servers[port.device_id]);
|
|
vmCount = Number(self.vis.selectAll('.vmCount').filter(filterNode(_network.data))[0][0].textContent);
|
|
self.vis.selectAll('.vmCount').filter(filterNode(_network.data))[0][0].textContent = vmCount + 1;
|
|
continue;
|
|
}
|
|
}
|
|
} else if (port.device_owner == 'network:router_interface') {
|
|
device.data.networks.push(_network.data);
|
|
device.data.ports.push(port);
|
|
} else if (device.data.ports) {
|
|
device.data.ports.push(port);
|
|
}
|
|
self.new_link(self.find_by_id(port.device_id), self.find_by_id(port.network_id));
|
|
change = true;
|
|
} else if (angular.isDefined(_network) && port.device_owner == 'compute:nova') {
|
|
// Need to add a previously hidden node to the graph because it is
|
|
// connected to more than 1 network
|
|
if (_network.data.collapsed) {
|
|
server = self.data.servers[port.device_id];
|
|
server.networks.push(_network.data);
|
|
if (port.fixed_ips) {
|
|
for(var ip in port.fixed_ips) {
|
|
server.ip_addresses.push(port.fixed_ips[ip]);
|
|
}
|
|
}
|
|
self.new_node(server);
|
|
// decrease collapsed vm count on network
|
|
vmCount = Number(self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent);
|
|
if (vmCount == 1) {
|
|
self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent = '';
|
|
} else {
|
|
self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent = vmCount - 1;
|
|
}
|
|
// Add back in first network link
|
|
self.new_link(self.find_by_id(port.device_id), self.find_by_id(server.networks[0].id));
|
|
// Add new link
|
|
self.new_link(self.find_by_id(port.device_id), self.find_by_id(port.network_id));
|
|
change = true;
|
|
}
|
|
}
|
|
}
|
|
self.data.ports[port.id+port.device_id+port.network_id] = port;
|
|
}
|
|
if (change) {
|
|
self.force.start();
|
|
}
|
|
self.load_config();
|
|
},
|
|
|
|
removeNode: function(obj) {
|
|
var filterNetwork, filterNode, n, node, _i, _len, _ref;
|
|
_ref = this.nodes;
|
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
n = _ref[_i];
|
|
if (n.data === obj) {
|
|
node = n;
|
|
break;
|
|
}
|
|
}
|
|
if (node) {
|
|
this.nodes.splice(this.nodes.indexOf(node), 1);
|
|
filterNode = function(obj) {
|
|
return function(d) {
|
|
return obj === d.data;
|
|
};
|
|
};
|
|
filterNetwork = function(obj) {
|
|
return function(d) {
|
|
return obj === d.network.data;
|
|
};
|
|
};
|
|
if (obj instanceof Network) {
|
|
this.vis.selectAll('.hulls').filter(filterNetwork(obj)).remove();
|
|
}
|
|
this.vis.selectAll('g.node').filter(filterNode(obj)).remove();
|
|
return this.removeNodesLinks(obj);
|
|
}
|
|
},
|
|
|
|
removeNodesLinks: function(node) {
|
|
var l, linksToRemove, _i, _j, _len, _len1, _ref;
|
|
linksToRemove = [];
|
|
_ref = this.links;
|
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
l = _ref[_i];
|
|
if (l.source.data === node) {
|
|
linksToRemove.push(l);
|
|
} else if (l.target.data === node) {
|
|
linksToRemove.push(l);
|
|
}
|
|
}
|
|
for (_j = 0, _len1 = linksToRemove.length; _j < _len1; _j++) {
|
|
l = linksToRemove[_j];
|
|
this.removeLink(l);
|
|
}
|
|
return this.force.resume();
|
|
},
|
|
|
|
removeLink: function(link) {
|
|
var i, index, l, _i, _len, _ref;
|
|
index = -1;
|
|
_ref = this.links;
|
|
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
|
|
l = _ref[i];
|
|
if (l === link) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
if (index !== -1) {
|
|
this.links.splice(index, 1);
|
|
}
|
|
return this.vis.selectAll('line.link').data(this.links).exit().remove();
|
|
},
|
|
|
|
delete_device: function(type, deviceId) {
|
|
var message = {id:deviceId};
|
|
horizon.networktopologymessager.post_message(deviceId,type,message,type,'delete',data={});
|
|
},
|
|
|
|
remove_node_on_delete: function(deleteData) {
|
|
var self = this;
|
|
var deviceId = deleteData.device_id;
|
|
switch (deleteData.device_type) {
|
|
case 'router':
|
|
self.removeNode(self.data.routers[deviceId]);
|
|
break;
|
|
case 'instance':
|
|
self.removeNode(self.data.servers[deviceId]);
|
|
this.data.servers[deviceId] = undefined;
|
|
break;
|
|
case 'network':
|
|
self.removeNode(self.data.networks[deviceId]);
|
|
break;
|
|
case 'port':
|
|
self.removePort(deviceId, deleteData.device_data);
|
|
break;
|
|
}
|
|
self.delete_balloon();
|
|
},
|
|
|
|
removePort: function(portId, deviceData) {
|
|
var self = this;
|
|
var routerId = deviceData.router_id;
|
|
var networkId = deviceData.network_id;
|
|
if (routerId) {
|
|
for (var l in self.links) {
|
|
var data = null;
|
|
if(self.links[l].source.data.id == routerId && self.links[l].target.data.id == networkId) {
|
|
data = self.links[l].source.data;
|
|
} else if (self.links[l].target.data.id == routerId && self.links[l].source.data.id == networkId) {
|
|
data = self.links[l].target.data;
|
|
}
|
|
if (data) {
|
|
for (var p in data.ports) {
|
|
if ((data.ports[p].id == portId) && (data.ports[p].network_id == networkId)) {
|
|
self.removeLink(self.links[l]);
|
|
// Update Router to remove deleted port
|
|
var router = self.find_by_id(routerId);
|
|
router.data.ports.splice(router.data.ports.indexOf(data.ports[p]), 1);
|
|
self.force.start();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
delete_port: function(routerId, portId, networkId) {
|
|
var message = {id:portId};
|
|
var data = {network_id:networkId,routerId:routerId};
|
|
if (routerId) {
|
|
horizon.networktopologymessager.post_message(portId, 'router/' + routerId + '/', message, 'port', 'delete', data);
|
|
} else {
|
|
horizon.networktopologymessager.post_message(portId, 'network/' + networkId + '/', message, 'port', 'delete', data);
|
|
}
|
|
},
|
|
|
|
show_balloon: function(d,d2,element) {
|
|
var self = this;
|
|
var balloonTmpl = self.balloonTmpl;
|
|
var deviceTmpl = self.balloon_deviceTmpl;
|
|
var portTmpl = self.balloon_portTmpl;
|
|
var netTmpl = self.balloon_netTmpl;
|
|
var instanceTmpl = self.balloon_instanceTmpl;
|
|
var balloonID = 'bl_' + d.id;
|
|
var ports = [];
|
|
var subnets = [];
|
|
if (self.balloonID) {
|
|
if (self.balloonID == balloonID) {
|
|
self.delete_balloon();
|
|
return;
|
|
}
|
|
self.delete_balloon();
|
|
}
|
|
self.force.stop();
|
|
if (d.hasOwnProperty('ports')) {
|
|
angular.element.each(d.ports, function(i, port) {
|
|
var object = {};
|
|
object.id = port.id;
|
|
object.router_id = port.device_id;
|
|
object.url = port.url;
|
|
object.port_status = port.status;
|
|
object.port_status_css = (port.original_status === 'ACTIVE') ? 'active' : 'down';
|
|
var ipAddress = '';
|
|
try {
|
|
for (var ip in port.fixed_ips) {
|
|
ipAddress += port.fixed_ips[ip].ip_address + ' ';
|
|
}
|
|
}catch(e) {
|
|
ipAddress = gettext('None');
|
|
}
|
|
var deviceOwner = '';
|
|
try {
|
|
deviceOwner = port.device_owner.replace('network:','');
|
|
}catch(e) {
|
|
deviceOwner = gettext('None');
|
|
}
|
|
var networkId = '';
|
|
try {
|
|
networkId = port.network_id;
|
|
}catch(e) {
|
|
networkId = gettext('None');
|
|
}
|
|
object.ip_address = ipAddress;
|
|
object.device_owner = deviceOwner;
|
|
object.network_id = networkId;
|
|
object.is_interface = (deviceOwner === 'router_interface' || deviceOwner === 'router_gateway');
|
|
ports.push(object);
|
|
});
|
|
} else if (d.hasOwnProperty('subnets')) {
|
|
angular.element.each(d.subnets, function(i, snet) {
|
|
var object = {};
|
|
object.id = snet.id;
|
|
object.cidr = snet.cidr;
|
|
object.url = snet.url;
|
|
subnets.push(object);
|
|
});
|
|
}
|
|
var htmlData = {
|
|
balloon_id:balloonID,
|
|
id:d.id,
|
|
url:d.url,
|
|
name:d.name,
|
|
type:d.type,
|
|
delete_label: gettext('Delete'),
|
|
status:d.status,
|
|
status_class: (d.original_status === 'ACTIVE') ? 'active' : 'down',
|
|
status_label: gettext('STATUS'),
|
|
id_label: gettext('ID'),
|
|
interfaces_label: gettext('Interfaces'),
|
|
subnets_label: gettext('Subnets'),
|
|
delete_interface_label: gettext('Delete Interface'),
|
|
delete_subnet_label: gettext('Delete Subnet'),
|
|
open_console_label: gettext('Open Console'),
|
|
view_details_label: gettext('View Details'),
|
|
ips_label: gettext('IP Addresses')
|
|
};
|
|
var html;
|
|
if (d instanceof Router) {
|
|
htmlData.delete_label = gettext('Delete Router');
|
|
htmlData.view_details_label = gettext('View Router Details');
|
|
htmlData.port = ports;
|
|
htmlData.add_interface_url = 'router/' + d.id + '/addinterface';
|
|
htmlData.add_interface_label = gettext('Add Interface');
|
|
html = balloonTmpl.render(htmlData,{
|
|
table1:deviceTmpl,
|
|
table2:portTmpl
|
|
});
|
|
} else if (d instanceof Server) {
|
|
htmlData.delete_label = gettext('Delete Instance');
|
|
htmlData.view_details_label = gettext('View Instance Details');
|
|
htmlData.console_id = d.id;
|
|
htmlData.ips = d.ip_addresses;
|
|
htmlData.console = d.console;
|
|
html = balloonTmpl.render(htmlData,{
|
|
table1:deviceTmpl,
|
|
table2:instanceTmpl
|
|
});
|
|
} else if (d instanceof Network || d instanceof ExternalNetwork) {
|
|
for (var s in subnets) {
|
|
subnets[s].network_id = d.id;
|
|
}
|
|
htmlData.subnet = subnets;
|
|
if (d instanceof Network) {
|
|
htmlData.delete_label = gettext('Delete Network');
|
|
}
|
|
htmlData.add_subnet_url = 'network/' + d.id + '/subnet/create';
|
|
htmlData.add_subnet_label = gettext('Create Subnet');
|
|
html = balloonTmpl.render(htmlData,{
|
|
table1:deviceTmpl,
|
|
table2:netTmpl
|
|
});
|
|
} else {
|
|
return;
|
|
}
|
|
angular.element(self.svg_container).append(html);
|
|
var devicePosition = self.getScreenCoords(d2.x, d2.y);
|
|
var x = devicePosition.x;
|
|
var y = devicePosition.y;
|
|
var xoffset = 100;
|
|
var yoffset = 95;
|
|
angular.element('#' + balloonID).css({
|
|
'left': x + xoffset + 'px',
|
|
'top': y + yoffset + 'px'
|
|
}).show();
|
|
var _balloon = angular.element('#' + balloonID);
|
|
if (element.x + _balloon.outerWidth() > angular.element(window).outerWidth()) {
|
|
_balloon
|
|
.css({
|
|
'left': 0 + 'px'
|
|
})
|
|
.css({
|
|
'left': (x - _balloon.outerWidth() + 'px')
|
|
})
|
|
.addClass('leftPosition');
|
|
}
|
|
_balloon.find('.delete-device').click(function() {
|
|
var _this = angular.element(this);
|
|
_this.prop('disabled', true);
|
|
d3.select('#id_' + _this.data('device-id')).classed('loading',true);
|
|
self.delete_device(_this.data('type'),_this.data('device-id'));
|
|
});
|
|
_balloon.find('.delete-port').click(function() {
|
|
var _this = angular.element(this);
|
|
self.delete_port(_this.data('router-id'),_this.data('port-id'),_this.data('network-id'));
|
|
self.delete_balloon();
|
|
});
|
|
self.balloonID = balloonID;
|
|
},
|
|
|
|
delete_balloon:function() {
|
|
var self = this;
|
|
if (self.balloonID) {
|
|
angular.element('#' + self.balloonID).remove();
|
|
self.balloonID = null;
|
|
self.force.start();
|
|
}
|
|
},
|
|
|
|
svgs: function(name) {
|
|
switch (name) {
|
|
case 'router':
|
|
return 'm 26.628571,16.08 -8.548572,0 0,8.548571 2.08,-2.079998 6.308572,6.30857 4.38857,-4.388572 -6.308571,-6.30857 z m -21.2571429,-4.159999 8.5485709,0 0,-8.5485723 -2.08,2.08 L 5.5314281,-0.85714307 1.1428571,3.5314287 7.4514281,9.84 z m -3.108571,7.268571 0,8.548571 8.5485709,0 L 8.7314281,25.657144 15.039999,19.325715 10.674285,14.96 4.3428571,21.268573 z M 29.737142,8.8114288 l 0,-8.54857147 -8.548572,0 2.08,2.07999987 -6.308571,6.3085716 4.388572,4.3885722 6.308571,-6.3085723 z';
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
};
|