awx/awx/ui/client/src/network-ui/network.ui.controller.js
Jared Tabor 883545d4cb
Fixes some bugs from PR feedback
* Fixes bug where new devices on the canvas weren't added to the search dropdown
* Fixes bug with closing the details panel
* Changes the fill color to white for remote-selected devices
* Fixes read-only mode by swapping out move controller for move read-only
* Updates range on the zoom widget
2018-03-23 17:00:28 -04:00

1463 lines
60 KiB
JavaScript

/* Copyright (c) 2017 Red Hat, Inc. */
var angular = require('angular');
var fsm = require('./fsm.js');
var mode_fsm = require('./mode.fsm.js');
var hotkeys = require('./hotkeys.fsm.js');
var toolbox_fsm = require('./toolbox.fsm.js');
var view = require('./view.fsm.js');
var move = require('./move.fsm.js');
var move_readonly = require('./move.readonly.fsm.js');
var buttons = require('./buttons.fsm.js');
var time = require('./time.fsm.js');
var test_fsm = require('./test.fsm.js');
var util = require('./util.js');
var models = require('./models.js');
var messages = require('./messages.js');
var animations = require('./animations.js');
var keybindings = require('./keybindings.fsm.js');
var details_panel_fsm = require('./details.panel.fsm.js');
var svg_crowbar = require('./vendor/svg-crowbar.js');
var ReconnectingWebSocket = require('reconnectingwebsocket');
var NetworkUIController = function($scope,
$document,
$location,
$window,
$http,
$q,
$state,
ProcessErrors,
ConfigService,
rbacUiControlService) {
window.scope = $scope;
$scope.http = $http;
$scope.api_token = '';
$scope.disconnected = false;
$scope.tests_enabled = false;
$scope.topology_id = 0;
// Create a web socket to connect to the backend server
$scope.inventory_id = $state.params.inventory_id;
var protocol = null;
if ($location.protocol() === 'http') {
protocol = 'ws';
} else if ($location.protocol() === 'https') {
protocol = 'wss';
}
$scope.initial_messages = [];
if (!$scope.disconnected) {
$scope.control_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/topology?inventory_id=" + $scope.inventory_id,
null,
{debug: false, reconnectInterval: 300});
if ($scope.tests_enabled) {
$scope.test_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/test?inventory_id=" + $scope.inventory_id,
null,
{debug: false, reconnectInterval: 300});
} else {
$scope.test_socket = {
on_message: util.noop,
send: util.noop
};
}
} else {
$scope.control_socket = {
on_message: util.noop
};
}
$scope.my_location = $location.protocol() + "://" + $location.host() + ':' + $location.port();
$scope.client_id = 0;
$scope.test_client_id = 0;
$scope.onMouseDownResult = "";
$scope.onMouseUpResult = "";
$scope.onMouseEnterResult = "";
$scope.onMouseLeaveResult = "";
$scope.onMouseMoveResult = "";
$scope.onMouseMoveResult = "";
$scope.current_scale = 1.0;
$scope.current_mode = null;
$scope.panX = 0;
$scope.panY = 0;
$scope.mouseX = 0;
$scope.mouseY = 0;
$scope.scaledX = 0;
$scope.scaledY = 0;
$scope.pressedX = 0;
$scope.pressedY = 0;
$scope.pressedScaledX = 0;
$scope.pressedScaledY = 0;
$scope.lastPanX = 0;
$scope.lastPanY = 0;
$scope.selected_devices = [];
$scope.selected_links = [];
$scope.selected_interfaces = [];
$scope.selected_items = [];
$scope.new_link = null;
$scope.new_stream = null;
$scope.last_key = "";
$scope.last_key_code = null;
$scope.last_event = null;
$scope.cursor = {'x':100, 'y': 100, 'hidden': true};
$scope.debug = {'hidden': true};
$scope.hide_buttons = false;
$scope.hide_menus = false;
$scope.hide_links = false;
$scope.hide_interfaces = false;
$scope.graph = {'width': window.innerWidth,
'right_column': 300,
'height': window.innerHeight};
$scope.MAX_ZOOM = 5;
$scope.MIN_ZOOM = 0.1;
$scope.device_id_seq = util.natural_numbers(0);
$scope.link_id_seq = util.natural_numbers(0);
$scope.message_id_seq = util.natural_numbers(0);
$scope.test_result_id_seq = util.natural_numbers(0);
$scope.animation_id_seq = util.natural_numbers(0);
$scope.overall_toolbox_collapsed = false;
$scope.time_pointer = -1;
$scope.frame = 0;
$scope.recording = false;
$scope.replay = false;
$scope.devices = [];
$scope.devices_by_name = {};
$scope.links = [];
$scope.links_in_vars_by_device = {};
$scope.tests = [];
$scope.current_tests = [];
$scope.current_test = null;
$scope.template_building = false;
$scope.version = null;
$scope.test_events = [];
$scope.test_results = [];
$scope.test_errors = [];
$scope.animations = [];
$scope.sequences = {};
$scope.view_port = {'x': 0,
'y': 0,
'width': 0,
'height': 0,
};
$scope.trace_id_seq = util.natural_numbers(0);
$scope.trace_order_seq = util.natural_numbers(0);
$scope.trace_id = $scope.trace_id_seq();
$scope.jump = {from_x: 0,
from_y: 0,
to_x: 0,
to_y: 0};
$scope.canEdit = $scope.$parent.$resolve.resolvedModels.canEdit;
$scope.send_trace_message = function (message) {
if (!$scope.recording) {
return;
}
message.sender = $scope.test_client_id;
message.trace_id = $scope.trace_id;
message.message_id = $scope.message_id_seq();
var data = messages.serialize(message);
if (!$scope.disconnected) {
try {
$scope.test_socket.send(data);
}
catch(err) {
$scope.initial_messages.push(message);
}
}
};
$scope.onKeyDown = function ($event) {
if ($scope.recording) {
$scope.send_test_message(new messages.KeyEvent($scope.test_client_id,
$event.key,
$event.keyCode,
$event.type,
$event.altKey,
$event.shiftKey,
$event.ctrlKey,
$event.metaKey,
$scope.trace_id));
}
$scope.last_event = $event;
$scope.last_key = $event.key;
$scope.last_key_code = $event.keyCode;
$scope.first_channel.send('KeyDown', $event);
$scope.$apply();
$event.preventDefault();
};
//Define the FSMs
$scope.hotkeys_controller = new fsm.FSMController($scope, "hotkeys_fsm", hotkeys.Start, $scope);
$scope.keybindings_controller = new fsm.FSMController($scope, "keybindings_fsm", keybindings.Start, $scope);
$scope.view_controller = new fsm.FSMController($scope, "view_fsm", view.Start, $scope);
$scope.move_controller = new fsm.FSMController($scope, "move_fsm", move.Start, $scope);
$scope.move_readonly_controller = new fsm.FSMController($scope, "move_readonly_fsm", move_readonly.Start, $scope);
$scope.details_panel_controller = new fsm.FSMController($scope, "details_panel_fsm", details_panel_fsm.Start, $scope);
$scope.buttons_controller = new fsm.FSMController($scope, "buttons_fsm", buttons.Start, $scope);
$scope.time_controller = new fsm.FSMController($scope, "time_fsm", time.Start, $scope);
$scope.test_controller = new fsm.FSMController($scope, "test_fsm", test_fsm.Start, $scope);
$scope.inventory_toolbox_controller = new fsm.FSMController($scope, "toolbox_fsm", toolbox_fsm.Start, $scope);
var toolboxTopMargin = $('.Networking-top').height();
var toolboxTitleMargin = toolboxTopMargin + 35;
var toolboxHeight = $scope.graph.height - $('.Networking-top').height();
$scope.update_links_in_vars_by_device = function (device_name, variables) {
var j = 0;
var link = null;
if (variables.ansible_topology !== undefined) {
if (variables.ansible_topology.links !== undefined) {
for (j=0; j < variables.ansible_topology.links.length; j++) {
link = variables.ansible_topology.links[j];
if (link.remote_device_name !== undefined &&
link.remote_interface_name !== undefined &&
link.name !== undefined) {
if ($scope.links_in_vars_by_device[device_name] === undefined) {
$scope.links_in_vars_by_device[device_name] = [];
}
if ($scope.links_in_vars_by_device[link.remote_device_name] === undefined) {
$scope.links_in_vars_by_device[link.remote_device_name] = [];
}
$scope.links_in_vars_by_device[device_name].push({
from_interface: link.name,
to_interface: link.remote_interface_name,
from_device: device_name,
to_device: link.remote_device_name
});
$scope.links_in_vars_by_device[link.remote_device_name].push({
from_interface: link.remote_interface_name,
to_interface: link.name,
from_device: link.remote_device_name,
to_device: device_name
});
}
}
}
}
};
$scope.for_each_page = function(url, callback, limit) {
function rec(url, rec_limit) {
if (rec_limit <= 0) {
return;
}
$http.get(url)
.then(function(response) {
callback(response.data.results);
if (response.data.next) {
rec(response.data.next, rec_limit-1);
}
})
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get host data: ' + status });
});
}
rec(url, limit);
};
//Inventory Toolbox Setup
$scope.inventory_toolbox = new models.ToolBox(0, 'Inventory', 'device', 0, toolboxTopMargin, 200, toolboxHeight);
if (!$scope.disconnected) {
$scope.for_each_page('/api/v2/inventories/' + $scope.inventory_id + '/hosts/',
function(all_results) {
let hosts = all_results;
console.log(hosts.length);
for(var i = 0; i<hosts.length; i++) {
console.log(i);
try {
let device_type = null;
let device_name = null;
let device = null;
let host = hosts[i];
device_name = host.name;
console.log(device_name);
if (host.variables !== "") {
host.data = jsyaml.safeLoad(host.variables);
console.log(host.data);
} else {
host.data = {};
}
if (host.data.ansible_topology === undefined) {
device_type = 'unknown';
} else {
if (host.data.ansible_topology.type === undefined) {
device_type = 'unknown';
} else {
device_type = host.data.ansible_topology.type;
}
$scope.update_links_in_vars_by_device(device_name, host.data);
}
if ($scope.devices_by_name[device_name] === undefined) {
console.log(['adding', device_name]);
device = new models.Device(0, device_name, 0, 0, device_type, host.id);
device.icon = true;
device.variables = host.data;
$scope.inventory_toolbox.items.push(device);
}
} catch (error) {
console.log(error);
}
}
}, 100);
}
$scope.inventory_toolbox.spacing = 150;
$scope.inventory_toolbox.enabled = true;
$scope.inventory_toolbox.title_coordinates = {x: 60, y: toolboxTitleMargin};
$scope.inventory_toolbox_controller.toolbox = $scope.inventory_toolbox;
$scope.inventory_toolbox_controller.remove_on_drop = true;
$scope.inventory_toolbox_controller.debug = true;
$scope.inventory_toolbox_controller.dropped_action = function (selected_item) {
$scope.first_channel.send("PasteDevice", new messages.PasteDevice(selected_item));
};
//End Inventory Toolbox Setup
$scope.mode_controller = new fsm.FSMController($scope, "mode_fsm", mode_fsm.Start, $scope);
//Wire up the FSMs
$scope.keybindings_controller.delegate_channel = new fsm.Channel($scope.keybindings_controller,
$scope.hotkeys_controller,
$scope);
$scope.view_controller.delegate_channel = new fsm.Channel($scope.view_controller,
$scope.keybindings_controller,
$scope);
if ($scope.canEdit) {
$scope.move_controller.delegate_channel = new fsm.Channel($scope.move_controller,
$scope.view_controller,
$scope);
$scope.details_panel_controller.delegate_channel = new fsm.Channel($scope.details_panel_controller,
$scope.move_controller,
$scope);
} else {
$scope.move_readonly_controller.delegate_channel = new fsm.Channel($scope.move_readonly_controller,
$scope.view_controller,
$scope);
$scope.details_panel_controller.delegate_channel = new fsm.Channel($scope.details_panel_controller,
$scope.move_readonly_controller,
$scope);
}
if ($scope.canEdit) {
$scope.inventory_toolbox_controller.delegate_channel = new fsm.Channel($scope.inventory_toolbox_controller,
$scope.details_panel_controller,
$scope);
$scope.buttons_controller.delegate_channel = new fsm.Channel($scope.buttons_controller,
$scope.inventory_toolbox_controller,
$scope);
} else {
$scope.buttons_controller.delegate_channel = new fsm.Channel($scope.buttons_controller,
$scope.details_panel_controller,
$scope);
}
$scope.time_controller.delegate_channel = new fsm.Channel($scope.time_controller,
$scope.buttons_controller,
$scope);
$scope.mode_controller.delegate_channel = new fsm.Channel($scope.mode_controller,
$scope.time_controller,
$scope);
$scope.test_controller.delegate_channel = new fsm.Channel($scope.test_controller,
$scope.mode_controller,
$scope);
$scope.first_channel = new fsm.Channel(null,
$scope.mode_controller,
$scope);
$scope.test_channel = new fsm.Channel(null,
$scope.test_controller,
$scope);
var getMouseEventResult = function (mouseEvent) {
return "(" + mouseEvent.x + ", " + mouseEvent.y + ")";
};
$scope.updateScaledXY = function() {
if (isNaN($scope.mouseX) ||
isNaN($scope.mouseY) ||
isNaN($scope.panX) ||
isNaN($scope.panY) ||
isNaN($scope.current_scale)) {
return;
}
$scope.scaledX = ($scope.mouseX - $scope.panX) / $scope.current_scale;
$scope.scaledY = ($scope.mouseY - $scope.panY) / $scope.current_scale;
$scope.view_port.x = - $scope.panX / $scope.current_scale;
$scope.view_port.y = - $scope.panY / $scope.current_scale;
$scope.view_port.width = $scope.graph.width / $scope.current_scale;
$scope.view_port.height = $scope.graph.height / $scope.current_scale;
};
$scope.updatePanAndScale = function() {
if (isNaN($scope.panX) ||
isNaN($scope.panY) ||
isNaN($scope.current_scale)) {
return;
}
var g = document.getElementById('frame_g');
g.setAttribute('transform','translate(' + $scope.panX + ',' + $scope.panY + ') scale(' + $scope.current_scale + ')');
};
$scope.to_virtual_coordinates = function (b_x, b_y) {
var v_x = (b_x - $scope.panX) / $scope.current_scale;
var v_y = (b_y - $scope.panY) / $scope.current_scale;
return {x: v_x, y: v_y};
};
$scope.to_pan = function (v_x, v_y) {
var p_x = v_x * $scope.current_scale * -1;
var p_y = v_y * $scope.current_scale * -1;
return {x: p_x, y: p_y};
};
$scope.clear_selections = function () {
var i = 0;
var j = 0;
var devices = $scope.devices;
var links = $scope.links;
$scope.selected_items = [];
$scope.selected_devices = [];
$scope.selected_links = [];
$scope.selected_interfaces = [];
for (i = 0; i < links.length; i++) {
if (links[i].selected) {
$scope.send_control_message(new messages.LinkUnSelected($scope.client_id, links[i].id));
}
links[i].selected = false;
}
for (i = 0; i < devices.length; i++) {
for (j = 0; j < devices[i].interfaces.length; j++) {
devices[i].interfaces[j].selected = false;
}
if (devices[i].selected) {
$scope.send_control_message(new messages.DeviceUnSelected($scope.client_id, devices[i].id));
}
devices[i].selected = false;
}
};
$scope.select_items = function (multiple_selection) {
var i = 0;
var j = 0;
var devices = $scope.devices;
var last_selected_device = null;
var last_selected_interface = null;
var last_selected_link = null;
$scope.pressedX = $scope.mouseX;
$scope.pressedY = $scope.mouseY;
$scope.pressedScaledX = $scope.scaledX;
$scope.pressedScaledY = $scope.scaledY;
if (!multiple_selection) {
$scope.clear_selections();
}
for (i = devices.length - 1; i >= 0; i--) {
if (devices[i].is_selected($scope.scaledX, $scope.scaledY)) {
devices[i].selected = true;
$scope.send_control_message(new messages.DeviceSelected($scope.client_id, devices[i].id));
last_selected_device = devices[i];
if ($scope.selected_items.indexOf($scope.devices[i]) === -1) {
$scope.selected_items.push($scope.devices[i]);
}
if ($scope.selected_devices.indexOf(devices[i]) === -1) {
$scope.selected_devices.push(devices[i]);
}
if (!multiple_selection) {
break;
}
}
}
// Do not select interfaces if a device was selected
if (last_selected_device === null && !$scope.hide_interfaces) {
for (i = devices.length - 1; i >= 0; i--) {
for (j = devices[i].interfaces.length - 1; j >= 0; j--) {
if (devices[i].interfaces[j].is_selected($scope.scaledX, $scope.scaledY)) {
devices[i].interfaces[j].selected = true;
last_selected_interface = devices[i].interfaces[j];
if ($scope.selected_interfaces.indexOf($scope.devices[i].interfaces[j]) === -1) {
$scope.selected_interfaces.push($scope.devices[i].interfaces[j]);
}
if ($scope.selected_items.indexOf($scope.devices[i].interfaces[j]) === -1) {
$scope.selected_items.push($scope.devices[i].interfaces[j]);
}
if (!multiple_selection) {
break;
}
}
}
}
}
// Do not select links if a device was selected
if (last_selected_device === null && last_selected_interface === null) {
for (i = $scope.links.length - 1; i >= 0; i--) {
if ($scope.links[i].is_selected($scope.scaledX, $scope.scaledY)) {
$scope.links[i].selected = true;
$scope.send_control_message(new messages.LinkSelected($scope.client_id, $scope.links[i].id));
last_selected_link = $scope.links[i];
if ($scope.selected_items.indexOf($scope.links[i]) === -1) {
$scope.selected_items.push($scope.links[i]);
}
if ($scope.selected_links.indexOf($scope.links[i]) === -1) {
$scope.selected_links.push($scope.links[i]);
if (!multiple_selection) {
break;
}
}
}
}
}
return {last_selected_device: last_selected_device,
last_selected_link: last_selected_link,
last_selected_interface: last_selected_interface,
};
};
// Event Handlers
$scope.normalize_mouse_event = function ($event) {
if ($event.pageX !== undefined) {
$event.x = $event.pageX;
}
if ($event.pageY !== undefined) {
$event.y = $event.pageY;
}
if ($event.originalEvent !== undefined) {
var originalEvent = $event.originalEvent;
if (originalEvent.wheelDelta !== undefined) {
$event.delta = $event.originalEvent.wheelDelta;
}
if (originalEvent.wheelDeltaX !== undefined) {
$event.deltaX = $event.originalEvent.wheelDeltaX;
}
if (originalEvent.wheelDeltaY !== undefined) {
$event.deltaY = $event.originalEvent.wheelDeltaY;
}
}
};
$scope.onMouseDown = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseDown', $event);
$scope.onMouseDownResult = getMouseEventResult($event);
$event.preventDefault();
};
$scope.onMouseUp = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseUp', $event);
$scope.onMouseUpResult = getMouseEventResult($event);
$event.preventDefault();
};
$scope.onMouseLeave = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.onMouseLeaveResult = getMouseEventResult($event);
$event.preventDefault();
};
$scope.onMouseMove = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
//var coords = getCrossBrowserElementCoords($event);
$scope.cursor.x = $event.x;
$scope.cursor.y = $event.y;
$scope.mouseX = $event.x;
$scope.mouseY = $event.y;
$scope.updateScaledXY();
$scope.first_channel.send('MouseMove', $event);
$scope.onMouseMoveResult = getMouseEventResult($event);
$event.preventDefault();
};
$scope.onMouseOver = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_test_message(new messages.MouseEvent($scope.test_client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.onMouseOverResult = getMouseEventResult($event);
$event.preventDefault();
};
$scope.onMouseEnter = $scope.onMouseOver;
$scope.onMouseWheel = function ($event) {
$scope.normalize_mouse_event($event);
var delta = $event.delta;
var deltaX = $event.deltaX;
var deltaY = $event.deltaY;
if ($scope.recording) {
$scope.send_test_message(new messages.MouseWheelEvent($scope.test_client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseWheel', [$event, delta, deltaX, deltaY]);
$event.preventDefault();
};
// Conext Menu Button Handlers
$scope.removeContextMenu = function(){
let context_menu = $scope.context_menus[0];
context_menu.enabled = false;
context_menu.x = -100000;
context_menu.y = -100000;
context_menu.buttons.forEach(function(button){
button.enabled = false;
button.x = -100000;
button.y = -100000;
});
};
$scope.closeDetailsPanel = function () {
$scope.first_channel.send('DetailsPanelClose', {});
};
$scope.$on('awxNet-closeDetailsPanel', $scope.closeDetailsPanel);
$scope.onDetailsContextButton = function () {
function emitCallback(item, canAdd){
$scope.first_channel.send('DetailsPanel', {});
$scope.removeContextMenu();
$scope.update_toolbox_heights();
$scope.$emit('awxNet-showDetails', item, canAdd);
}
// show details for devices
if ($scope.selected_devices.length === 1 && $scope.selected_devices[0].host_id === 0){
// following block is intended for devices added in the network UI but not in Tower
emitCallback($scope.selected_devices[0]);
}
// following block is intended for devices that are saved in the API
if ($scope.selected_devices.length === 1 && $scope.selected_devices[0].host_id !== 0){
let host_id = $scope.selected_devices[0].host_id;
let url = `/api/v2/hosts/${host_id}/`;
let hostData = $http.get(url)
.then(function(response) {
let host = response.data;
host.host_id = host.id;
return host;
})
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get host data: ' + status });
});
let canAdd = rbacUiControlService.canAdd('hosts')
.then(function(res) {
return res.canAdd;
})
.catch(function() {
return false;
});
Promise.all([hostData, canAdd]).then((values) => {
let item = values[0];
let canAdd = values[1];
emitCallback(item, canAdd);
});
}
// show details for interfaces
else if($scope.selected_interfaces.length === 1){
emitCallback($scope.selected_interfaces[0]);
}
// show details for links
else if($scope.selected_links.length === 1){
emitCallback($scope.selected_links[0]);
}
};
$scope.onDeviceDestroy = function(data) {
$scope.destroy_device(data);
};
$scope.destroy_device = function(data) {
// Delete the device and any links connecting to the device.
var i = 0;
var j = 0;
var dindex = -1;
var lindex = -1;
var devices = $scope.devices.slice();
var all_links = $scope.links.slice();
for (i = 0; i < devices.length; i++) {
if (devices[i].id === data.id) {
dindex = $scope.devices.indexOf(devices[i]);
if (dindex !== -1) {
$scope.devices.splice(dindex, 1);
}
lindex = -1;
for (j = 0; j < all_links.length; j++) {
if (all_links[j].to_device === devices[i] ||
all_links[j].from_device === devices[i]) {
lindex = $scope.links.indexOf(all_links[j]);
if (lindex !== -1) {
$scope.links.splice(lindex, 1);
}
}
}
}
}
};
$scope.deleteDevice = function(){
var i = 0;
var j = 0;
var index = -1;
var devices = $scope.selected_devices;
var all_links = $scope.links.slice();
$scope.selected_devices = [];
$scope.selected_links = [];
$scope.move_controller.changeState(move.Ready);
for (i = 0; i < devices.length; i++) {
index = $scope.devices.indexOf(devices[i]);
if (index !== -1) {
$scope.devices.splice(index, 1);
$scope.devices_by_name[devices[i].name] = undefined;
$scope.$emit('awxNet-removeSearchOption', devices[i]);
devices[i].x = 0;
devices[i].y = 0;
devices[i].selected = false;
devices[i].remote_selected = false;
devices[i].interfaces = [];
devices[i].interfaces_by_name = [];
$scope.inventory_toolbox.items.push(devices[i]);
$scope.send_control_message(new messages.DeviceDestroy($scope.client_id,
devices[i].id,
devices[i].x,
devices[i].y,
devices[i].name,
devices[i].type,
devices[i].host_id));
}
for (j = 0; j < all_links.length; j++) {
if (all_links[j].to_device === devices[i] ||
all_links[j].from_device === devices[i]) {
index = $scope.links.indexOf(all_links[j]);
if (index !== -1) {
$scope.links.splice(index, 1);
$scope.send_control_message(new messages.LinkDestroy($scope.client_id,
all_links[j].id,
all_links[j].from_device.id,
all_links[j].to_device.id,
all_links[j].from_interface.id,
all_links[j].to_interface.id,
all_links[j].name));
}
}
}
}
};
$scope.onDeleteContextMenu = function(){
$scope.removeContextMenu();
if($scope.selected_devices.length === 1){
$scope.deleteDevice();
}
};
$scope.$on('awxNet-hideToolbox', () => {
$scope.first_channel.send("ToggleToolbox", {});
$scope.overall_toolbox_collapsed = !$scope.overall_toolbox_collapsed;
});
$scope.$on('awxNet-toolbarButtonEvent', function(e, functionName){
$scope[`on${functionName}Button`]();
});
$scope.$on('awxNet-SearchDropdown', function(){
$scope.first_channel.send('SearchDropdown', {});
});
$scope.$on('awxNet-SearchDropdownClose', function(){
$scope.first_channel.send('SearchDropdownClose', {});
});
$scope.$on('awxNet-search', function(e, device){
var searched;
for(var i = 0; i < $scope.devices.length; i++){
if(Number(device.id) === $scope.devices[i].id){
searched = $scope.devices[i];
}
}
searched.selected = true;
$scope.selected_devices.push(searched);
$scope.jump_to_animation(searched.x, searched.y, 1.0);
});
$scope.jump_to_animation = function(jump_to_x, jump_to_y, jump_to_scale, updateZoom) {
$scope.cancel_animations();
var v_center = $scope.to_virtual_coordinates($scope.graph.width/2, $scope.graph.height/2);
$scope.jump.from_x = v_center.x;
$scope.jump.from_y = v_center.y;
$scope.jump.to_x = jump_to_x;
$scope.jump.to_y = jump_to_y;
var distance = util.distance(v_center.x, v_center.y, jump_to_x, jump_to_y);
var num_frames = 30 * Math.floor((1 + 4 * distance / (distance + 3000)));
var scale_animation = new models.Animation($scope.animation_id_seq(),
num_frames,
{
c: -0.1,
distance: distance,
end_height: (1.0/jump_to_scale) - 1,
current_scale: $scope.current_scale,
scope: $scope,
updateZoomBoolean: updateZoom
},
$scope,
$scope,
animations.scale_animation);
$scope.animations.push(scale_animation);
var pan_animation = new models.Animation($scope.animation_id_seq(),
num_frames,
{
x2: jump_to_x,
y2: jump_to_y,
x1: v_center.x,
y1: v_center.y,
scope: $scope
},
$scope,
$scope,
animations.pan_animation);
$scope.animations.push(pan_animation);
};
$scope.$on('awxNet-zoom', (e, zoomPercent) => {
let v_center = $scope.to_virtual_coordinates($scope.graph.width/2, $scope.graph.height/2);
let scale = Math.pow(10, (zoomPercent - 120) / 120);
$scope.jump_to_animation(v_center.x, v_center.y, scale, false);
});
$scope.onRecordButton = function () {
$scope.recording = ! $scope.recording;
if ($scope.recording) {
$scope.trace_id = $scope.trace_id_seq();
$scope.send_test_message(new messages.MultipleMessage($scope.test_client_id,
[new messages.StartRecording($scope.test_client_id, $scope.trace_id),
new messages.ViewPort($scope.test_client_id,
$scope.current_scale,
$scope.panX,
$scope.panY,
$scope.graph.width,
$scope.graph.height,
$scope.trace_id),
new messages.Snapshot($scope.test_client_id,
$scope.devices,
$scope.links,
$scope.inventory_toolbox.items,
0,
$scope.trace_id)]));
} else {
$scope.send_test_message(new messages.MultipleMessage($scope.test_client_id,
[new messages.Snapshot($scope.test_client_id,
$scope.devices,
$scope.links,
$scope.inventory_toolbox.items,
1,
$scope.trace_id),
new messages.StopRecording($scope.test_client_id, $scope.trace_id)]));
}
};
$scope.onExportButton = function () {
$scope.cursor.hidden = true;
$scope.debug.hidden = true;
$scope.hide_buttons = true;
$scope.hide_menus = true;
setTimeout(function () {
svg_crowbar.svg_crowbar();
$scope.cursor.hidden = false;
$scope.hide_buttons = false;
$scope.hide_menus = false;
$scope.$apply();
}, 1000);
};
$scope.onExportYamlButton = function () {
$window.open('/network_ui/topology.yaml?topology_id=' + $scope.topology_id , '_blank');
};
// Context Menu Buttons
const contextMenuButtonHeight = 26;
let contextMenuHeight = 64;
$scope.context_menu_buttons = [
new models.ContextMenuButton("Details", 236, 231, 160, contextMenuButtonHeight, $scope.onDetailsContextButton, $scope),
new models.ContextMenuButton("Remove", 256, 231, 160, contextMenuButtonHeight, $scope.onDeleteContextMenu, $scope)
];
if(!$scope.canEdit){
$scope.context_menu_buttons.pop();
contextMenuHeight = $scope.context_menu_buttons.length * contextMenuButtonHeight + 12;
}
// Context Menus
$scope.context_menus = [
new models.ContextMenu('HOST', 210, 200, 160, contextMenuHeight, $scope.contextMenuCallback, false, $scope.context_menu_buttons, $scope)
];
$scope.onDownloadTraceButton = function () {
window.open("/network_ui_test/download_trace?topology_id=" + $scope.topology_id + "&trace_id=" + $scope.trace_id + "&client_id=" + $scope.test_client_id);
};
$scope.onDownloadRecordingButton = function () {
window.open("/network_ui_test/download_recording?topology_id=" + $scope.topology_id + "&trace_id=" + $scope.trace_id + "&client_id=" + $scope.test_client_id);
};
$scope.onUploadTestButton = function () {
window.open("/network_ui_test/upload_test", "_top");
};
$scope.onRunTestsButton = function () {
$scope.test_results = [];
$scope.current_tests = $scope.tests.slice();
$scope.test_channel.send("EnableTest", new messages.EnableTest());
};
$scope.all_buttons = $scope.context_menu_buttons;
$scope.onDeviceCreate = function(data) {
$scope.create_device(data);
};
$scope.create_device = function(data) {
console.log(data);
var device = new models.Device(data.id,
data.name,
data.x,
data.y,
data.type,
data.host_id);
$scope.device_id_seq = util.natural_numbers(data.id);
$scope.devices.push(device);
$scope.devices_by_name[device.name] = device;
};
$scope.onInterfaceCreate = function(data) {
$scope.create_interface(data);
};
$scope.create_interface = function(data) {
var i = 0;
var new_interface = new models.Interface(data.id, data.name);
for (i = 0; i < $scope.devices.length; i++){
if ($scope.devices[i].id === data.device_id) {
$scope.devices[i].interface_seq = util.natural_numbers(data.id);
new_interface.device = $scope.devices[i];
$scope.devices[i].interfaces.push(new_interface);
}
}
};
$scope.onLinkCreate = function(data) {
console.log(data);
$scope.create_link(data);
};
$scope.create_link = function(data) {
var i = 0;
var j = 0;
var new_link = new models.Link(null, null, null, null);
new_link.id = data.id;
$scope.link_id_seq = util.natural_numbers(data.id);
for (i = 0; i < $scope.devices.length; i++){
if ($scope.devices[i].id === data.from_device_id) {
new_link.from_device = $scope.devices[i];
for (j = 0; j < $scope.devices[i].interfaces.length; j++){
if ($scope.devices[i].interfaces[j].id === data.from_interface_id) {
new_link.from_interface = $scope.devices[i].interfaces[j];
$scope.devices[i].interfaces[j].link = new_link;
}
}
}
}
for (i = 0; i < $scope.devices.length; i++){
if ($scope.devices[i].id === data.to_device_id) {
new_link.to_device = $scope.devices[i];
for (j = 0; j < $scope.devices[i].interfaces.length; j++){
if ($scope.devices[i].interfaces[j].id === data.to_interface_id) {
new_link.to_interface = $scope.devices[i].interfaces[j];
$scope.devices[i].interfaces[j].link = new_link;
}
}
}
}
console.log(new_link);
if (new_link.from_interface !== null && new_link.to_interface !== null) {
new_link.from_interface.dot();
new_link.to_interface.dot();
}
if (new_link.from_device !== null && new_link.to_device !== null) {
$scope.links.push(new_link);
}
};
$scope.onLinkDestroy = function(data) {
$scope.destroy_link(data);
};
$scope.destroy_link = function(data) {
var i = 0;
var link = null;
var index = -1;
for (i = 0; i < $scope.links.length; i++) {
link = $scope.links[i];
if (link.id === data.id &&
link.from_device.id === data.from_device_id &&
link.to_device.id === data.to_device_id &&
link.to_interface.id === data.to_interface_id &&
link.from_interface.id === data.from_interface_id) {
link.from_interface.link = null;
link.to_interface.link = null;
index = $scope.links.indexOf(link);
$scope.links.splice(index, 1);
}
}
};
$scope.onDeviceMove = function(data) {
$scope.move_device(data);
};
$scope.move_device = function(data) {
var i = 0;
var j = 0;
for (i = 0; i < $scope.devices.length; i++) {
if ($scope.devices[i].id === data.id) {
$scope.devices[i].x = data.x;
$scope.devices[i].y = data.y;
for (j = 0; j < $scope.devices[i].interfaces.length; j ++) {
$scope.devices[i].interfaces[j].dot();
if ($scope.devices[i].interfaces[j].link !== null) {
$scope.devices[i].interfaces[j].link.to_interface.dot();
$scope.devices[i].interfaces[j].link.from_interface.dot();
}
}
break;
}
}
};
$scope.onClientId = function(data) {
$scope.client_id = data;
};
$scope.onTopology = function(data) {
$scope.topology_id = data.topology_id;
$scope.panX = data.panX;
$scope.panY = data.panX;
$scope.current_scale = data.scale;
$scope.$emit('awxNet-UpdateZoomWidget', $scope.current_scale, true);
$scope.link_id_seq = util.natural_numbers(data.link_id_seq);
$scope.device_id_seq = util.natural_numbers(data.device_id_seq);
};
$scope.onDeviceSelected = function(data) {
var i = 0;
for (i = 0; i < $scope.devices.length; i++) {
if ($scope.devices[i].id === data.id) {
$scope.devices[i].remote_selected = true;
}
}
};
$scope.onDeviceUnSelected = function(data) {
var i = 0;
for (i = 0; i < $scope.devices.length; i++) {
if ($scope.devices[i].id === data.id) {
$scope.devices[i].remote_selected = false;
}
}
};
$scope.onSnapshot = function (data) {
//Erase the existing state
$scope.devices = [];
$scope.links = [];
$scope.devices_by_name = {};
var device_map = {};
var device_interface_map = {};
var i = 0;
var j = 0;
var device = null;
var intf = null;
var new_device = null;
var new_intf = null;
var max_device_id = null;
var max_link_id = null;
var min_x = null;
var min_y = null;
var max_x = null;
var max_y = null;
var new_link = null;
//Build the devices
for (i = 0; i < data.devices.length; i++) {
device = data.devices[i];
if (max_device_id === null || device.id > max_device_id) {
max_device_id = device.id;
}
if (min_x === null || device.x < min_x) {
min_x = device.x;
}
if (min_y === null || device.y < min_y) {
min_y = device.y;
}
if (max_x === null || device.x > max_x) {
max_x = device.x;
}
if (max_y === null || device.y > max_y) {
max_y = device.y;
}
if (device.device_type === undefined) {
device.device_type = device.type;
}
new_device = new models.Device(device.id,
device.name,
device.x,
device.y,
device.device_type,
device.host_id);
if (device.variables !== undefined) {
new_device.variables = device.variables;
}
for (j=0; j < $scope.inventory_toolbox.items.length; j++) {
if($scope.inventory_toolbox.items[j].name === device.name) {
$scope.inventory_toolbox.items.splice(j, 1);
break;
}
}
new_device.interface_seq = util.natural_numbers(device.interface_id_seq);
new_device.process_id_seq = util.natural_numbers(device.process_id_seq);
$scope.devices.push(new_device);
$scope.devices_by_name[new_device.name] = new_device;
device_map[device.id] = new_device;
device_interface_map[device.id] = {};
for (j = 0; j < device.interfaces.length; j++) {
intf = device.interfaces[j];
new_intf = (new models.Interface(intf.id,
intf.name));
new_intf.device = new_device;
device_interface_map[device.id][intf.id] = new_intf;
new_device.interfaces.push(new_intf);
new_device.interfaces_by_name[new_intf.name] = new_intf;
}
}
//Build the links
var link = null;
for (i = 0; i < data.links.length; i++) {
link = data.links[i];
if (max_link_id === null || link.id > max_link_id) {
max_link_id = link.id;
}
new_link = new models.Link(link.id,
device_map[link.from_device_id],
device_map[link.to_device_id],
device_interface_map[link.from_device_id][link.from_interface_id],
device_interface_map[link.to_device_id][link.to_interface_id]);
new_link.name = link.name;
$scope.links.push(new_link);
device_interface_map[link.from_device_id][link.from_interface_id].link = new_link;
device_interface_map[link.to_device_id][link.to_interface_id].link = new_link;
}
var diff_x;
var diff_y;
// Calculate the new scale to show the entire diagram
if (min_x !== null && min_y !== null && max_x !== null && max_y !== null) {
diff_x = max_x - min_x;
diff_y = max_y - min_y;
$scope.current_scale = Math.min(2, Math.max(0.10, Math.min((window.innerWidth-200)/diff_x, (window.innerHeight-300)/diff_y)));
$scope.$emit('awxNet-UpdateZoomWidget', $scope.current_scale, true);
$scope.updateScaledXY();
$scope.updatePanAndScale();
}
// Calculate the new panX and panY to show the entire diagram
if (min_x !== null && min_y !== null) {
diff_x = max_x - min_x;
diff_y = max_y - min_y;
$scope.panX = $scope.current_scale * (-min_x - diff_x/2) + window.innerWidth/2;
$scope.panY = $scope.current_scale * (-min_y - diff_y/2) + window.innerHeight/2;
$scope.updateScaledXY();
$scope.updatePanAndScale();
}
//Update the device_id_seq to be greater than all device ids to prevent duplicate ids.
if (max_device_id !== null) {
$scope.device_id_seq = util.natural_numbers(max_device_id);
}
//
//Update the link_id_seq to be greater than all link ids to prevent duplicate ids.
if (max_link_id !== null) {
$scope.link_id_seq = util.natural_numbers(max_link_id);
}
console.log(['data.inventory_toolbox', data.inventory_toolbox]);
if (data.inventory_toolbox !== undefined) {
$scope.inventory_toolbox.items = [];
for (i = 0; i < data.inventory_toolbox.length; i++) {
device = data.inventory_toolbox[i];
console.log(device);
if (device.device_type === undefined) {
device.device_type = device.type;
}
new_device = new models.Device(device.id,
device.name,
device.x,
device.y,
device.device_type,
device.host_id);
if (device.variables !== undefined) {
new_device.variables = device.variables;
}
$scope.inventory_toolbox.items.push(new_device);
}
console.log($scope.inventory_toolbox.items);
}
$scope.updateInterfaceDots();
$scope.$emit('awxNet-instatiateSelect', $scope.devices);
};
$scope.updateInterfaceDots = function() {
var i = 0;
var j = 0;
var devices = $scope.devices;
for (i = devices.length - 1; i >= 0; i--) {
for (j = devices[i].interfaces.length - 1; j >= 0; j--) {
devices[i].interfaces[j].dot();
}
}
};
$scope.control_socket.onmessage = function(message) {
$scope.first_channel.send('Message', message);
$scope.$apply();
};
$scope.control_socket.onopen = function() {
//ignore
};
$scope.test_socket.onmessage = function(message) {
$scope.test_channel.send('Message', message);
$scope.$apply();
};
$scope.test_socket.onopen = function() {
//ignore
};
// Call onopen directly if $scope.control_socket is already open
if ($scope.control_socket.readyState === WebSocket.OPEN) {
$scope.control_socket.onopen();
}
// Call onopen directly if $scope.test_socket is already open
if ($scope.test_socket.readyState === WebSocket.OPEN) {
$scope.test_socket.onopen();
}
$scope.send_test_message = function (message) {
var i = 0;
message.sender = $scope.test_client_id;
message.message_id = $scope.message_id_seq();
if (message.constructor.name === "MultipleMessage") {
for (i=0; i < message.messages.length; i++) {
message.messages[i].message_id = $scope.message_id_seq();
}
}
var data = messages.serialize(message);
if (!$scope.disconnected) {
$scope.test_socket.send(data);
}
};
$scope.send_control_message = function (message) {
var i = 0;
message.sender = $scope.client_id;
message.message_id = $scope.message_id_seq();
if (message.constructor.name === "MultipleMessage") {
for (i=0; i < message.messages.length; i++) {
message.messages[i].message_id = $scope.message_id_seq();
}
}
var data = messages.serialize(message);
if (!$scope.disconnected) {
$scope.control_socket.send(data);
}
};
// End web socket
//
angular.element($window).bind('resize', function(){
$scope.graph.width = $window.innerWidth;
$scope.graph.right_column = 300;
$scope.graph.height = $window.innerHeight;
$scope.update_size();
// manuall $digest required as resize event
// is outside of angular
$scope.$digest();
});
//60fps ~ 17ms delay
setInterval( function () {
$scope.frame = Math.floor(window.performance.now());
$scope.$apply();
}, 17);
console.log("Network UI started");
$scope.$on('$destroy', function () {
console.log("Network UI stopping");
$scope.first_channel.send('UnbindDocument', {});
});
$scope.update_toolbox_heights = function(){
var toolboxTopMargin = $('.Networking-top').height();
var toolboxTitleMargin = toolboxTopMargin + 35;
var toolboxHeight = $scope.graph.height - toolboxTopMargin;
let toolboxes = ['inventory_toolbox'];
toolboxes.forEach((toolbox) => {
$scope[toolbox].y = toolboxTopMargin;
$scope[toolbox].height = toolboxHeight;
$scope[toolbox].title_coordinates.y = toolboxTitleMargin;
});
$('.Networking-detailPanel').height(toolboxHeight);
$('.Networking-detailPanel').css('top', toolboxTopMargin);
};
$scope.update_size = function () {
$scope.update_toolbox_heights();
};
setInterval( function () {
var test_event = null;
if ($scope.test_events.length > 0) {
test_event = $scope.test_events.shift();
test_event.sender = 0;
try {
$scope.first_channel.send(test_event.msg_type, test_event);
} catch (err) {
console.log(["Test Error:", $scope.current_test, err]);
$scope.test_errors.push(err);
}
}
$scope.$apply();
}, 10);
ConfigService
.getConfig()
.then(function(config){
$scope.version = config.version;
});
$scope.reset_coverage = function() {
var i = null;
var coverage = null;
var f = null;
if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) {
for (f in window.__coverage__) {
coverage = window.__coverage__[f];
for (i in coverage.b) {
coverage.b[i] = [0, 0];
}
for (i in coverage.f) {
coverage.f[i] = 0;
}
for (i in coverage.s) {
coverage.s[i] = 0;
}
}
}
};
$scope.reset_flags = function () {
$scope.debug = {'hidden': true};
$scope.hide_buttons = false;
$scope.hide_links = false;
$scope.hide_interfaces = false;
};
$scope.reset_fsm_state = function () {
$scope.hotkeys_controller.state = hotkeys.Start;
$scope.hotkeys_controller.state.start($scope.hotkeys_controller);
$scope.keybindings_controller.state = keybindings.Start;
$scope.keybindings_controller.state.start($scope.keybindings_controller);
$scope.view_controller.state = view.Start;
$scope.view_controller.state.start($scope.view_controller);
$scope.move_controller.state = move.Start;
$scope.move_controller.state.start($scope.move_controller);
$scope.move_readonly_controller.state = move_readonly.Start;
$scope.move_readonly_controller.state.start($scope.move_readonly_controller);
$scope.details_panel_controller.state = details_panel_fsm.Start;
$scope.details_panel_controller.state.start($scope.details_panel_controller);
$scope.time_controller.state = time.Start;
$scope.time_controller.state.start($scope.time_controller);
$scope.inventory_toolbox_controller.state = toolbox_fsm.Start;
$scope.inventory_toolbox_controller.state.start($scope.inventory_toolbox_controller);
$scope.mode_controller.state = mode_fsm.Start;
$scope.mode_controller.state.start($scope.mode_controller);
};
$scope.reset_toolboxes = function () {
$scope.inventory_toolbox.items = [];
$scope.inventory_toolbox.scroll_offset = 0;
};
$scope.cancel_animations = function () {
var i = 0;
for (i = 0; i < $scope.animations.length; i++) {
this.animations[i].fsm.handle_message('AnimationCancelled');
}
$scope.animations = [];
};
};
exports.NetworkUIController = NetworkUIController;
console.log("Network UI loaded");