4a3f0dcef0
Network topology "Graph" tab always says "There are no networks,
routers, or connected instances to display."
This happens after a fix for bug 1661350
(commit f35eaa2d13
) was merged.
The problem with that in checking the length of object.
So always will be equal undefined,
also check the object before getting filled.
Closes-Bug: #1747196
Change-Id: I72dd8d2b472677411635f58309c780a8d602ffe3
1108 lines
35 KiB
JavaScript
1108 lines
35 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;
|
|
|
|
self.$loading_template = horizon.networktopologyloader.setup_loader($(self.svg_container));
|
|
|
|
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').change(function() {
|
|
horizon.cookies.put('show_labels', this.checked);
|
|
self.refresh_labels();
|
|
});
|
|
|
|
angular.element('#toggle_networks').change(function() {
|
|
horizon.cookies.put('are_networks_collapsed', this.checked);
|
|
self.refresh_networks();
|
|
self.refresh_labels();
|
|
});
|
|
|
|
// set up loader first thing
|
|
self.$loading_template.show();
|
|
|
|
self.create_vis();
|
|
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);
|
|
if(angular.equals(self.data.networks,{}) && angular.equals(self.data.routers,{}) &&
|
|
angular.equals(self.data.servers,{})){
|
|
$('.loader-inline').remove();
|
|
angular.element('#topologyCanvasContainer').find('svg').remove();
|
|
$(self.svg_container).addClass('noinfo');
|
|
return;
|
|
}
|
|
});
|
|
|
|
// register for message notifications
|
|
horizon.networktopologymessager.addMessageHandler(
|
|
this.handleMessage, this
|
|
);
|
|
},
|
|
|
|
// Shows/Hides graph labels
|
|
refresh_labels: function() {
|
|
var show_labels = horizon.cookies.get('show_labels') == 'true';
|
|
angular.element('.nodeLabel').toggle(show_labels);
|
|
},
|
|
|
|
// Collapses/Uncollapses networks in the graph
|
|
refresh_networks: function() {
|
|
var self = this;
|
|
var are_collapsed = horizon.cookies.get('are_networks_collapsed') == 'true';
|
|
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], are_collapsed);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Load config from cookie
|
|
load_config: function() {
|
|
var self = this;
|
|
|
|
var labels = horizon.cookies.get('show_labels') == 'true';
|
|
var networks = horizon.cookies.get('are_networks_collapsed') == 'true';
|
|
|
|
if(networks) {
|
|
angular.element('#toggle_networks_label').addClass('active');
|
|
angular.element('#toggle_networks').prop('checked', networks);
|
|
self.refresh_networks();
|
|
}
|
|
|
|
if(labels) {
|
|
angular.element('#toggle_labels_label').addClass('active');
|
|
angular.element('#toggle_labels').prop('checked', labels);
|
|
self.refresh_labels();
|
|
}
|
|
},
|
|
|
|
handleMessage:function(message) {
|
|
var self = this;
|
|
var deleteData = horizon.networktopologymessager.delete_data;
|
|
horizon.modals.spinner.modal('hide');
|
|
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++;
|
|
}
|
|
}
|
|
},
|
|
|
|
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').find('svg').remove();
|
|
|
|
// 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');
|
|
},
|
|
|
|
// 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();
|
|
self.$loading_template.hide();
|
|
},
|
|
|
|
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(device_type, deviceId) {
|
|
var message = {id:deviceId};
|
|
var target = device_type === 'instance' ? 'instance?id=' + deviceId : device_type;
|
|
horizon.networktopologymessager.post_message(deviceId, target, message, device_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.removePortOrSubnet(deviceId, deleteData.device_data);
|
|
break;
|
|
}
|
|
self.delete_balloon();
|
|
},
|
|
|
|
removePortOrSubnet: 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var networkData = self.find_by_id(networkId).data;
|
|
var subnets = networkData.subnets;
|
|
for (var subnet in subnets) {
|
|
if (subnets[subnet].id === portId) {
|
|
if (subnets.length == 1) {
|
|
delete(networkData.subnets);
|
|
} else {
|
|
subnets.splice(subnet, 1);
|
|
}
|
|
self.force.start();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
delete_port: function(routerId, portId, networkId) {
|
|
var message = {id:portId};
|
|
var data = {network_id:networkId,router_id:routerId};
|
|
if (routerId) {
|
|
horizon.networktopologymessager.post_message(portId, 'router/' + routerId + '/', message, 'port', 'delete', data);
|
|
} else {
|
|
horizon.networktopologymessager.post_message(portId, 'network/' + networkId + '/?tab=network_tabs__subnets_tab', 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_class = (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' ||
|
|
deviceOwner === 'ha_router_replicated_interface'
|
|
);
|
|
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);
|
|
var delete_modal = horizon.datatables.confirm(_this);
|
|
delete_modal.find('.btn.btn-danger').click(function () {
|
|
_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);
|
|
var delete_modal = horizon.datatables.confirm(_this);
|
|
delete_modal.find('.btn.btn-danger').click(function () {
|
|
_this.prop('disabled', true);
|
|
self.delete_port(_this.data('router-id'),_this.data('port-id'),_this.data('network-id'));
|
|
});
|
|
});
|
|
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 '';
|
|
}
|
|
}
|
|
};
|