horizon/openstack_dashboard/static/js/horizon.networktopology.js
Akihiro Motoki ffbe0e2f2f network topology: handle port AZ correctly
Previously d3 version of the network topology view handles ports with
device_owner 'compute:nova' and 'compute:None' specially. This leads
to the situtaion that neutron ports with non-default AZ are not shown
properly. There is no reason to handle neutron ports with the default
AZ differently. What we would like to do is just to classify neutron
ports attached to nova servers.

This commit changes the logic to check device_owner of ports have
a prefix 'compute:'. Note that we also need to check if device_owner
is a string before checking the prefix.

Change-Id: I472bae9af3d14d8a31efcae8a7610b84c4f09c3d
Closes-Bug: #1820260
2019-03-21 19:17:05 +09:00

1142 lines
36 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();
});
angular.element('#center_topology').click(function() {
this.blur(); // remove btn focus after click
self.delete_balloon();
// move visualization to the center and reset scale
self.vis.transition()
.duration(1500)
.attr('transform', 'translate(0,0)scale(1)');
// reset internal zoom translate and scale parameters so on next
// move the objects do not jump to the old position
self.zoom.translate([0,0]);
self.zoom.scale(1);
self.translate = null;
});
angular.element(window).on('message', function(e) {
var message = angular.element.parseJSON(e.originalEvent.data);
if (self.previous_message !== message.message) {
horizon.alert(message.type, message.message);
self.previous_message = message.message;
self.delete_post_message(message.iframe_id);
if (message.type == 'success' && self.deleting_device) {
self.remove_node_on_delete();
}
self.retrieve_network_info();
setTimeout(function() {
self.previous_message = null;
},10000);
}
});
// 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 && port.device_owner.startsWith('compute:')) {
_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 && port.device_owner.startsWith('compute:')) {
// 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');
if (d.allow_delete_subnet){
htmlData.allow_delete_subnet = d.allow_delete_subnet;
}
}
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 '';
}
}
};