mirror of
https://github.com/ansible/awx.git
synced 2026-02-17 11:10:03 -03:30
Adds network UI shell wrapper
* Adds networking icons, state, and shell
* Adds network UI to the network UI shell.
* Removes jquery as a dependency of network-ui
* Fills the entire viewport with the network canvas and
makes header panel and the right panel overlay on
top of it
This commit is contained in:
committed by
Ben Thomasson
parent
09d461b1d0
commit
2a8ced5a5d
@@ -29,7 +29,7 @@ istanbul:
|
|||||||
|
|
||||||
|
|
||||||
simple-server: lint main lessc
|
simple-server: lint main lessc
|
||||||
python -m SimpleHTTPServer 8080
|
python -m SimpleHTTPServer 8081
|
||||||
|
|
||||||
|
|
||||||
deploy: main
|
deploy: main
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var time = require('./time.js');
|
|||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
var models = require('./models.js');
|
var models = require('./models.js');
|
||||||
var messages = require('./messages.js');
|
var messages = require('./messages.js');
|
||||||
var svg_crowbar = require('../vendor/svg-crowbar.js');
|
var svg_crowbar = require('./svg-crowbar.js');
|
||||||
var ReconnectingWebSocket = require('reconnectingwebsocket');
|
var ReconnectingWebSocket = require('reconnectingwebsocket');
|
||||||
|
|
||||||
var NetworkUIController = function($scope, $document, $location, $window, $http) {
|
var NetworkUIController = function($scope, $document, $location, $window, $http) {
|
||||||
@@ -26,7 +26,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
$scope.api_token = '';
|
$scope.api_token = '';
|
||||||
$scope.disconnected = false;
|
$scope.disconnected = true;
|
||||||
|
|
||||||
$scope.topology_id = $location.search().topology_id || 0;
|
$scope.topology_id = $location.search().topology_id || 0;
|
||||||
// Create a web socket to connect to the backend server
|
// Create a web socket to connect to the backend server
|
||||||
@@ -77,13 +77,13 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
$scope.last_event = null;
|
$scope.last_event = null;
|
||||||
$scope.cursor = {'x':100, 'y': 100, 'hidden': false};
|
$scope.cursor = {'x':100, 'y': 100, 'hidden': false};
|
||||||
|
|
||||||
$scope.debug = {'hidden': true};
|
$scope.debug = {'hidden': false};
|
||||||
$scope.hide_buttons = false;
|
$scope.hide_buttons = false;
|
||||||
$scope.hide_links = false;
|
$scope.hide_links = false;
|
||||||
$scope.hide_interfaces = false;
|
$scope.hide_interfaces = false;
|
||||||
$scope.hide_groups = false;
|
$scope.hide_groups = false;
|
||||||
$scope.graph = {'width': window.innerWidth,
|
$scope.graph = {'width': window.innerWidth,
|
||||||
'right_column': window.innerWidth - 300,
|
'right_column': 300,
|
||||||
'height': window.innerHeight};
|
'height': window.innerHeight};
|
||||||
$scope.device_id_seq = util.natural_numbers(0);
|
$scope.device_id_seq = util.natural_numbers(0);
|
||||||
$scope.link_id_seq = util.natural_numbers(0);
|
$scope.link_id_seq = util.natural_numbers(0);
|
||||||
@@ -208,15 +208,28 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateScaledXY = function() {
|
$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.scaledX = ($scope.mouseX - $scope.panX) / $scope.current_scale;
|
||||||
$scope.scaledY = ($scope.mouseY - $scope.panY) / $scope.current_scale;
|
$scope.scaledY = ($scope.mouseY - $scope.panY) / $scope.current_scale;
|
||||||
$scope.view_port.x = - $scope.panX / $scope.current_scale;
|
$scope.view_port.x = - $scope.panX / $scope.current_scale;
|
||||||
$scope.view_port.y = - $scope.panY / $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.width = $scope.graph.width / $scope.current_scale;
|
||||||
$scope.view_port.height = $scope.graph.height / $scope.current_scale;
|
$scope.view_port.height = $scope.graph.height / $scope.current_scale;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updatePanAndScale = function() {
|
$scope.updatePanAndScale = function() {
|
||||||
|
if (isNaN($scope.panX) ||
|
||||||
|
isNaN($scope.panY) ||
|
||||||
|
isNaN($scope.current_scale)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var g = document.getElementById('frame_g');
|
var g = document.getElementById('frame_g');
|
||||||
g.setAttribute('transform','translate(' + $scope.panX + ',' + $scope.panY + ') scale(' + $scope.current_scale + ')');
|
g.setAttribute('transform','translate(' + $scope.panX + ',' + $scope.panY + ') scale(' + $scope.current_scale + ')');
|
||||||
};
|
};
|
||||||
@@ -337,7 +350,13 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
|
|
||||||
// Event Handlers
|
// Event Handlers
|
||||||
|
|
||||||
|
$scope.normalize_mouse_event = function ($event) {
|
||||||
|
$event.x = $event.pageX;
|
||||||
|
$event.y = $event.pageY;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.onMouseDown = function ($event) {
|
$scope.onMouseDown = function ($event) {
|
||||||
|
$scope.normalize_mouse_event($event);
|
||||||
if ($scope.recording) {
|
if ($scope.recording) {
|
||||||
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
||||||
}
|
}
|
||||||
@@ -348,6 +367,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.onMouseUp = function ($event) {
|
$scope.onMouseUp = function ($event) {
|
||||||
|
$scope.normalize_mouse_event($event);
|
||||||
if ($scope.recording) {
|
if ($scope.recording) {
|
||||||
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
||||||
}
|
}
|
||||||
@@ -358,6 +378,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.onMouseLeave = function ($event) {
|
$scope.onMouseLeave = function ($event) {
|
||||||
|
$scope.normalize_mouse_event($event);
|
||||||
if ($scope.recording) {
|
if ($scope.recording) {
|
||||||
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
||||||
}
|
}
|
||||||
@@ -367,6 +388,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.onMouseMove = function ($event) {
|
$scope.onMouseMove = function ($event) {
|
||||||
|
$scope.normalize_mouse_event($event);
|
||||||
if ($scope.recording) {
|
if ($scope.recording) {
|
||||||
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
||||||
}
|
}
|
||||||
@@ -383,6 +405,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.onMouseOver = function ($event) {
|
$scope.onMouseOver = function ($event) {
|
||||||
|
$scope.normalize_mouse_event($event);
|
||||||
if ($scope.recording) {
|
if ($scope.recording) {
|
||||||
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
|
||||||
}
|
}
|
||||||
@@ -393,7 +416,11 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
|
|
||||||
$scope.onMouseEnter = $scope.onMouseOver;
|
$scope.onMouseEnter = $scope.onMouseOver;
|
||||||
|
|
||||||
$scope.onMouseWheel = function ($event, delta, deltaX, deltaY) {
|
$scope.onMouseWheel = function ($event) {
|
||||||
|
var delta = $event.originalEvent.wheelDelta;
|
||||||
|
var deltaX = $event.originalEvent.wheelDeltaX;
|
||||||
|
var deltaY = $event.originalEvent.wheelDeltaY;
|
||||||
|
console.log([$event, delta, deltaX, deltaY]);
|
||||||
if ($scope.recording) {
|
if ($scope.recording) {
|
||||||
$scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey));
|
$scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey));
|
||||||
}
|
}
|
||||||
@@ -1462,7 +1489,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http)
|
|||||||
angular.element($window).bind('resize', function(){
|
angular.element($window).bind('resize', function(){
|
||||||
|
|
||||||
$scope.graph.width = $window.innerWidth;
|
$scope.graph.width = $window.innerWidth;
|
||||||
$scope.graph.right_column = $window.innerWidth - 300;
|
$scope.graph.right_column = 300;
|
||||||
$scope.graph.height = $window.innerHeight;
|
$scope.graph.height = $window.innerHeight;
|
||||||
|
|
||||||
$scope.update_size();
|
$scope.update_size();
|
||||||
|
|||||||
87
awx/network_ui/static/network_ui/src/ngTouch.js
Normal file
87
awx/network_ui/static/network_ui/src/ngTouch.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* ngTouch.js v1.0.2
|
||||||
|
* (c) 2015 Mark Topper
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
angular.module("ngTouch", [])
|
||||||
|
.directive("ngTouchstart", function () {
|
||||||
|
return {
|
||||||
|
controller: ["$scope", "$element", function ($scope, $element) {
|
||||||
|
|
||||||
|
$element.bind("touchstart", onTouchStart);
|
||||||
|
function onTouchStart(event) {
|
||||||
|
var method = $element.attr("ng-touchstart");
|
||||||
|
$scope.$event = event;
|
||||||
|
$scope.$apply(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.directive("ngTouchmove", function () {
|
||||||
|
return {
|
||||||
|
controller: ["$scope", "$element", function ($scope, $element) {
|
||||||
|
|
||||||
|
$element.bind("touchstart", onTouchStart);
|
||||||
|
function onTouchStart(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$element.bind("touchmove", onTouchMove);
|
||||||
|
$element.bind("touchend", onTouchEnd);
|
||||||
|
}
|
||||||
|
function onTouchMove(event) {
|
||||||
|
var method = $element.attr("ng-touchmove");
|
||||||
|
$scope.$event = event;
|
||||||
|
$scope.$apply(method);
|
||||||
|
}
|
||||||
|
function onTouchEnd(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$element.unbind("touchmove", onTouchMove);
|
||||||
|
$element.unbind("touchend", onTouchEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.directive("ngTouchend", function () {
|
||||||
|
return {
|
||||||
|
controller: ["$scope", "$element", function ($scope, $element) {
|
||||||
|
|
||||||
|
$element.bind("touchend", onTouchEnd);
|
||||||
|
function onTouchEnd(event) {
|
||||||
|
var method = $element.attr("ng-touchend");
|
||||||
|
$scope.$event = event;
|
||||||
|
$scope.$apply(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.directive("ngTap", function () {
|
||||||
|
return {
|
||||||
|
controller: ["$scope", "$element", function ($scope, $element) {
|
||||||
|
|
||||||
|
var moved = false;
|
||||||
|
$element.bind("touchstart", onTouchStart);
|
||||||
|
function onTouchStart() {
|
||||||
|
$element.bind("touchmove", onTouchMove);
|
||||||
|
$element.bind("touchend", onTouchEnd);
|
||||||
|
}
|
||||||
|
function onTouchMove() {
|
||||||
|
moved = true;
|
||||||
|
}
|
||||||
|
function onTouchEnd() {
|
||||||
|
$element.unbind("touchmove", onTouchMove);
|
||||||
|
$element.unbind("touchend", onTouchEnd);
|
||||||
|
if (!moved) {
|
||||||
|
var method = $element.attr("ng-tap");
|
||||||
|
$scope.$apply(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
});
|
||||||
250
awx/network_ui/static/network_ui/src/svg-crowbar.js
Normal file
250
awx/network_ui/static/network_ui/src/svg-crowbar.js
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* @license svg-crowbar
|
||||||
|
* (c) 2013 The New York Times
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
function svg_crowbar () {
|
||||||
|
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
|
||||||
|
|
||||||
|
window.URL = (window.URL || window.webkitURL);
|
||||||
|
|
||||||
|
var body = document.body;
|
||||||
|
|
||||||
|
var prefix = {
|
||||||
|
xmlns: "http://www.w3.org/2000/xmlns/",
|
||||||
|
xlink: "http://www.w3.org/1999/xlink",
|
||||||
|
svg: "http://www.w3.org/2000/svg"
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
var documents = [window.document],
|
||||||
|
SVGSources = [],
|
||||||
|
iframes = document.querySelectorAll("iframe"),
|
||||||
|
objects = document.querySelectorAll("object");
|
||||||
|
|
||||||
|
[].forEach.call(iframes, function(el) {
|
||||||
|
try {
|
||||||
|
if (el.contentDocument) {
|
||||||
|
documents.push(el.contentDocument);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[].forEach.call(objects, function(el) {
|
||||||
|
try {
|
||||||
|
if (el.contentDocument) {
|
||||||
|
documents.push(el.contentDocument);
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
documents.forEach(function(doc) {
|
||||||
|
var styles = getStyles(doc);
|
||||||
|
var newSources = getSources(doc, styles);
|
||||||
|
// because of prototype on NYT pages
|
||||||
|
for (var i = 0; i < newSources.length; i++) {
|
||||||
|
SVGSources.push(newSources[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (SVGSources.length > 1) {
|
||||||
|
createPopover(SVGSources);
|
||||||
|
} else if (SVGSources.length > 0) {
|
||||||
|
download(SVGSources[0]);
|
||||||
|
} else {
|
||||||
|
alert("The Crowbar couldn’t find any SVG nodes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPopover(sources) {
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
sources.forEach(function(s1) {
|
||||||
|
sources.forEach(function(s2) {
|
||||||
|
if (s1 !== s2) {
|
||||||
|
if ((Math.abs(s1.top - s2.top) < 38) && (Math.abs(s1.left - s2.left) < 38)) {
|
||||||
|
s2.top += 38;
|
||||||
|
s2.left += 38;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var buttonsContainer = document.createElement("div");
|
||||||
|
body.appendChild(buttonsContainer);
|
||||||
|
|
||||||
|
buttonsContainer.setAttribute("class", "svg-crowbar");
|
||||||
|
buttonsContainer.style["z-index"] = 1e7;
|
||||||
|
buttonsContainer.style.position = "absolute";
|
||||||
|
buttonsContainer.style.top = 0;
|
||||||
|
buttonsContainer.style.left = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var background = document.createElement("div");
|
||||||
|
body.appendChild(background);
|
||||||
|
|
||||||
|
background.setAttribute("class", "svg-crowbar");
|
||||||
|
background.style.background = "rgba(255, 255, 255, 0.7)";
|
||||||
|
background.style.position = "fixed";
|
||||||
|
background.style.left = 0;
|
||||||
|
background.style.top = 0;
|
||||||
|
background.style.width = "100%";
|
||||||
|
background.style.height = "100%";
|
||||||
|
|
||||||
|
sources.forEach(function(d, i) {
|
||||||
|
var buttonWrapper = document.createElement("div");
|
||||||
|
buttonsContainer.appendChild(buttonWrapper);
|
||||||
|
buttonWrapper.setAttribute("class", "svg-crowbar");
|
||||||
|
buttonWrapper.style.position = "absolute";
|
||||||
|
buttonWrapper.style.top = (d.top + document.body.scrollTop) + "px";
|
||||||
|
buttonWrapper.style.left = (document.body.scrollLeft + d.left) + "px";
|
||||||
|
buttonWrapper.style.padding = "4px";
|
||||||
|
buttonWrapper.style["border-radius"] = "3px";
|
||||||
|
buttonWrapper.style.color = "white";
|
||||||
|
buttonWrapper.style["text-align"] = "center";
|
||||||
|
buttonWrapper.style["font-family"] = "'Helvetica Neue'";
|
||||||
|
buttonWrapper.style.background = "rgba(0, 0, 0, 0.8)";
|
||||||
|
buttonWrapper.style["box-shadow"] = "0px 4px 18px rgba(0, 0, 0, 0.4)";
|
||||||
|
buttonWrapper.style.cursor = "move";
|
||||||
|
buttonWrapper.textContent = "SVG #" + i + ": " + (d.id ? "#" + d.id : "") + (d.class ? "." + d.class : "");
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
buttonWrapper.appendChild(button);
|
||||||
|
button.setAttribute("data-source-id", i);
|
||||||
|
button.style.width = "150px";
|
||||||
|
button.style["font-size"] = "12px";
|
||||||
|
button.style["line-height"] = "1.4em";
|
||||||
|
button.style.margin = "5px 0 0 0";
|
||||||
|
button.textContent = "Download";
|
||||||
|
|
||||||
|
button.onclick = function() {
|
||||||
|
// console.log(el, d, i, sources)
|
||||||
|
download(d);
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
var crowbarElements = document.querySelectorAll(".svg-crowbar");
|
||||||
|
|
||||||
|
[].forEach.call(crowbarElements, function(el) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getSources(doc, styles) {
|
||||||
|
var svgInfo = [],
|
||||||
|
svgs = doc.querySelectorAll("svg");
|
||||||
|
|
||||||
|
styles = (styles === undefined) ? "" : styles;
|
||||||
|
|
||||||
|
[].forEach.call(svgs, function (svg) {
|
||||||
|
|
||||||
|
svg.setAttribute("version", "1.1");
|
||||||
|
|
||||||
|
var defsEl = document.createElement("defs");
|
||||||
|
svg.insertBefore(defsEl, svg.firstChild); //TODO .insert("defs", ":first-child")
|
||||||
|
// defsEl.setAttribute("class", "svg-crowbar");
|
||||||
|
|
||||||
|
var styleEl = document.createElement("style");
|
||||||
|
defsEl.appendChild(styleEl);
|
||||||
|
styleEl.setAttribute("type", "text/css");
|
||||||
|
|
||||||
|
|
||||||
|
// removing attributes so they aren't doubled up
|
||||||
|
svg.removeAttribute("xmlns");
|
||||||
|
svg.removeAttribute("xlink");
|
||||||
|
|
||||||
|
// These are needed for the svg
|
||||||
|
if (!svg.hasAttributeNS(prefix.xmlns, "xmlns")) {
|
||||||
|
svg.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!svg.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) {
|
||||||
|
svg.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink);
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = (new XMLSerializer()).serializeToString(svg).replace('</style>', '<![CDATA[' + styles + ']]></style>');
|
||||||
|
var rect = svg.getBoundingClientRect();
|
||||||
|
svgInfo.push({
|
||||||
|
top: rect.top,
|
||||||
|
left: rect.left,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
class: svg.getAttribute("class"),
|
||||||
|
id: svg.getAttribute("id"),
|
||||||
|
childElementCount: svg.childElementCount,
|
||||||
|
source: [doctype + source]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return svgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(source) {
|
||||||
|
var filename = "untitled";
|
||||||
|
|
||||||
|
if (source.id) {
|
||||||
|
filename = source.id;
|
||||||
|
} else if (source.class) {
|
||||||
|
filename = source.class;
|
||||||
|
} else if (window.document.title) {
|
||||||
|
filename = window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = window.URL.createObjectURL(new Blob(source.source, { "type" : "text\/xml" }));
|
||||||
|
|
||||||
|
var a = document.createElement("a");
|
||||||
|
body.appendChild(a);
|
||||||
|
a.setAttribute("class", "svg-crowbar");
|
||||||
|
a.setAttribute("download", filename + ".svg");
|
||||||
|
a.setAttribute("href", url);
|
||||||
|
a.style.display = "none";
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyles(doc) {
|
||||||
|
var styles = "",
|
||||||
|
styleSheets = doc.styleSheets;
|
||||||
|
|
||||||
|
if (styleSheets) {
|
||||||
|
for (var i = 0; i < styleSheets.length; i++) {
|
||||||
|
processStyleSheet(styleSheets[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processStyleSheet(ss) {
|
||||||
|
if (ss.cssRules) {
|
||||||
|
for (var i = 0; i < ss.cssRules.length; i++) {
|
||||||
|
var rule = ss.cssRules[i];
|
||||||
|
if (rule.type === 3) {
|
||||||
|
// Import Rule
|
||||||
|
processStyleSheet(rule.styleSheet);
|
||||||
|
} else {
|
||||||
|
// hack for illustrator crashing on descendent selectors
|
||||||
|
if (rule.selectorText) {
|
||||||
|
if (rule.selectorText.indexOf(">") === -1) {
|
||||||
|
styles += "\n" + rule.cssText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
exports.svg_crowbar = svg_crowbar;
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
var angular = require('angular');
|
var angular = require('angular');
|
||||||
var ui_router = require('angular-ui-router');
|
|
||||||
|
|
||||||
var tower = angular.module('tower', ['tablesUI', 'networkUI', 'ui.router']);
|
var tower = angular.module('tower', ['tablesUI', 'networkUI', 'ui.router']);
|
||||||
|
|
||||||
@@ -31,5 +30,4 @@ tower.config(function($stateProvider, $urlRouterProvider) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
exports.tower = tower;
|
exports.tower = tower;
|
||||||
exports.ui_router = ui_router;
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
@import 'credentials/_index';
|
@import 'credentials/_index';
|
||||||
@import 'users/tokens/_index';
|
@import 'users/tokens/_index';
|
||||||
|
@import 'networking/_index';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import atFeaturesApplications from '~features/applications';
|
|||||||
import atFeaturesCredentials from '~features/credentials';
|
import atFeaturesCredentials from '~features/credentials';
|
||||||
import atFeaturesTemplates from '~features/templates';
|
import atFeaturesTemplates from '~features/templates';
|
||||||
import atFeaturesUsers from '~features/users';
|
import atFeaturesUsers from '~features/users';
|
||||||
|
import atFeaturesNetworking from '~features/networking';
|
||||||
|
|
||||||
const MODULE_NAME = 'at.features';
|
const MODULE_NAME = 'at.features';
|
||||||
|
|
||||||
@@ -16,7 +17,8 @@ angular.module(MODULE_NAME, [
|
|||||||
atFeaturesApplications,
|
atFeaturesApplications,
|
||||||
atFeaturesCredentials,
|
atFeaturesCredentials,
|
||||||
atFeaturesTemplates,
|
atFeaturesTemplates,
|
||||||
atFeaturesUsers
|
atFeaturesUsers,
|
||||||
|
atFeaturesNetworking
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default MODULE_NAME;
|
export default MODULE_NAME;
|
||||||
|
|||||||
72
awx/ui/client/features/networking/_index.less
Normal file
72
awx/ui/client/features/networking/_index.less
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
.Networking-header{
|
||||||
|
border: 1px solid @at-color-panel-border;
|
||||||
|
display:flex;
|
||||||
|
height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
width:100%;
|
||||||
|
background-color: @default-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-headerTitle{
|
||||||
|
color: @default-interface-txt;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 10px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Netowrking-headerActions{
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-headerActionItem{
|
||||||
|
justify-content: flex-end;
|
||||||
|
display: flex;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-actionButton{
|
||||||
|
font-size: 16px;
|
||||||
|
height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
color: @default-icon;
|
||||||
|
background-color: inherit;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-actionButton:hover{
|
||||||
|
background-color:@default-link;
|
||||||
|
color: @default-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-panels{
|
||||||
|
display:flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-leftPanel{
|
||||||
|
width:100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.Networking-rightPanel{
|
||||||
|
border-left: 1px solid @at-color-panel-border;
|
||||||
|
display:flex;
|
||||||
|
width:400px;
|
||||||
|
height: calc(~"100vh - 40px");
|
||||||
|
padding: 20px;
|
||||||
|
color: @default-interface-txt;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
position: absolute;
|
||||||
|
top: 40px;
|
||||||
|
right: 0px;
|
||||||
|
background-color: @default-bg;
|
||||||
|
}
|
||||||
55
awx/ui/client/features/networking/index.js
Normal file
55
awx/ui/client/features/networking/index.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import NetworkingController from './networking.controller';
|
||||||
|
import NetworkingStrings from './networking.strings';
|
||||||
|
|
||||||
|
const MODULE_NAME = 'at.features.networking';
|
||||||
|
|
||||||
|
const networkingTemplate = require('~features/networking/networking.view.html');
|
||||||
|
|
||||||
|
function NetworkingResolve ($stateParams, resourceData) {
|
||||||
|
const resolve = {
|
||||||
|
inventory: {
|
||||||
|
id: $stateParams.inventory_id,
|
||||||
|
name: $stateParams.inventory_name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!resolve.inventory.name) {
|
||||||
|
resolve.inventory.name = resourceData.data.name;
|
||||||
|
}
|
||||||
|
return resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkingResolve.$inject = [
|
||||||
|
'$stateParams',
|
||||||
|
'resourceData'
|
||||||
|
];
|
||||||
|
function NetworkingRun ($stateExtender, strings) {
|
||||||
|
$stateExtender.addState({
|
||||||
|
name: 'inventories.edit.networking',
|
||||||
|
route: '/networking',
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: strings.get('state.BREADCRUMB_LABEL')
|
||||||
|
},
|
||||||
|
views: {
|
||||||
|
'networking@': {
|
||||||
|
templateUrl: networkingTemplate,
|
||||||
|
controller: NetworkingController,
|
||||||
|
controllerAs: 'vm'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
resolvedModels: NetworkingResolve
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkingRun.$inject = [
|
||||||
|
'$stateExtender',
|
||||||
|
'NetworkingStrings'
|
||||||
|
];
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module(MODULE_NAME, [])
|
||||||
|
.service('NetworkingStrings', NetworkingStrings)
|
||||||
|
.run(NetworkingRun);
|
||||||
|
|
||||||
|
export default MODULE_NAME;
|
||||||
29
awx/ui/client/features/networking/networking.controller.js
Normal file
29
awx/ui/client/features/networking/networking.controller.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
function NetworkingController (models, $state, $scope, strings) {
|
||||||
|
const vm = this || {};
|
||||||
|
|
||||||
|
const {
|
||||||
|
inventory
|
||||||
|
} = models;
|
||||||
|
|
||||||
|
vm.strings = strings;
|
||||||
|
vm.panelTitle = `${strings.get('state.BREADCRUMB_LABEL')} | ${inventory.name}`;
|
||||||
|
|
||||||
|
vm.panelIsExpanded = true;
|
||||||
|
|
||||||
|
vm.togglePanel = () => {
|
||||||
|
vm.panelIsExpanded = !vm.panelIsExpanded;
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.close = () => {
|
||||||
|
$state.go('inventories');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkingController.$inject = [
|
||||||
|
'resolvedModels',
|
||||||
|
'$state',
|
||||||
|
'$scope',
|
||||||
|
'NetworkingStrings'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default NetworkingController;
|
||||||
19
awx/ui/client/features/networking/networking.strings.js
Normal file
19
awx/ui/client/features/networking/networking.strings.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
function NetworkingStrings (BaseString) {
|
||||||
|
BaseString.call(this, 'networking');
|
||||||
|
|
||||||
|
const { t } = this;
|
||||||
|
const ns = this.networking;
|
||||||
|
|
||||||
|
ns.state = {
|
||||||
|
BREADCRUMB_LABEL: t.s('INVENTORIES'),
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.actions = {
|
||||||
|
EXPAND_PANEL: t.s('Expand Panel'),
|
||||||
|
COLLAPSE_PANEL: t.s('Collapse Panel')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkingStrings.$inject = ['BaseStringService'];
|
||||||
|
|
||||||
|
export default NetworkingStrings;
|
||||||
36
awx/ui/client/features/networking/networking.view.html
Normal file
36
awx/ui/client/features/networking/networking.view.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="Networking-header">
|
||||||
|
<div class="Networking-headerTitle">{{vm.panelTitle}}</div>
|
||||||
|
<div class="Netowrking-headerActions">
|
||||||
|
<div class="Networking-headerActionItem">
|
||||||
|
<button class="Networking-actionButton"
|
||||||
|
aw-tool-tip="Expand Output"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-original-title="Expand Output"
|
||||||
|
ng-click="vm.togglePanel()"
|
||||||
|
ng-hide="vm.panelIsExpanded">
|
||||||
|
<i class="fa fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<button class="Networking-actionButton"
|
||||||
|
aw-tool-tip="Collapse Output"
|
||||||
|
data-placement="left"
|
||||||
|
data-original-title="Collapse Output"
|
||||||
|
ng-click="vm.togglePanel()"
|
||||||
|
ng-hide="!vm.panelIsExpanded">
|
||||||
|
<i class="fa fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="Networking-headerActionItem">
|
||||||
|
<button ng-click="vm.close()" type="button" class="close">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Networking-panels">
|
||||||
|
<div class="Networking-leftPanel">
|
||||||
|
<awx-network-ui></awx-network-ui>
|
||||||
|
</div>
|
||||||
|
<div class="Networking-rightPanel" ng-show="vm.panelIsExpanded">
|
||||||
|
VIEW LAYERS
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-user-agent="{{userAgent}}">
|
<body data-user-agent="{{userAgent}}">
|
||||||
|
<div ui-view="networking"><div>
|
||||||
<at-layout>
|
<at-layout>
|
||||||
<bread-crumb></bread-crumb>
|
<bread-crumb></bread-crumb>
|
||||||
<toast></toast>
|
<toast></toast>
|
||||||
|
|||||||
@@ -170,3 +170,9 @@
|
|||||||
* the transition.
|
* the transition.
|
||||||
*/
|
*/
|
||||||
@import '_resets';
|
@import '_resets';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network Visualization Style
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@import '../../src/network_ui/style.less';
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import atLibComponents from '~components';
|
|||||||
import atLibModels from '~models';
|
import atLibModels from '~models';
|
||||||
import atLibServices from '~services';
|
import atLibServices from '~services';
|
||||||
|
|
||||||
|
import network_ui from './network_ui/main';
|
||||||
|
|
||||||
start.bootstrap(() => {
|
start.bootstrap(() => {
|
||||||
angular.bootstrap(document.body, ['awApp']);
|
angular.bootstrap(document.body, ['awApp']);
|
||||||
});
|
});
|
||||||
@@ -97,6 +99,8 @@ angular
|
|||||||
users.name,
|
users.name,
|
||||||
projects.name,
|
projects.name,
|
||||||
scheduler.name,
|
scheduler.name,
|
||||||
|
instanceGroups.name,
|
||||||
|
network_ui.tower.name,
|
||||||
|
|
||||||
'Utilities',
|
'Utilities',
|
||||||
'templates',
|
'templates',
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ export default ['i18n', function(i18n) {
|
|||||||
|
|
||||||
fieldActions: {
|
fieldActions: {
|
||||||
columnClass: 'col-md-2 col-sm-3 col-xs-4',
|
columnClass: 'col-md-2 col-sm-3 col-xs-4',
|
||||||
|
network: {
|
||||||
|
label: i18n._('Network Visualization'),
|
||||||
|
ngClick: 'goToGraph(inventory)',
|
||||||
|
awToolTip: i18n._('Network Visualization'),
|
||||||
|
dataPlacement: 'top',
|
||||||
|
ngShow: '!inventory.pending_deletion && inventory.summary_fields.user_capabilities.edit'
|
||||||
|
},
|
||||||
edit: {
|
edit: {
|
||||||
label: i18n._('Edit'),
|
label: i18n._('Edit'),
|
||||||
ngClick: 'editInventory(inventory)',
|
ngClick: 'editInventory(inventory)',
|
||||||
|
|||||||
@@ -73,16 +73,10 @@ function InventoriesList($scope,
|
|||||||
inventory.linkToDetails = (inventory.kind && inventory.kind === 'smart') ? `inventories.editSmartInventory({smartinventory_id:${inventory.id}})` : `inventories.edit({inventory_id:${inventory.id}})`;
|
inventory.linkToDetails = (inventory.kind && inventory.kind === 'smart') ? `inventories.editSmartInventory({smartinventory_id:${inventory.id}})` : `inventories.edit({inventory_id:${inventory.id}})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.copyInventory = inventory => {
|
$scope.goToGraph = function(inventory){
|
||||||
Wait('start');
|
$state.go('inventories.edit.networking', {inventory_id: inventory.id, inventory_name: inventory.name});
|
||||||
new Inventory('get', inventory.id)
|
// let url = $state.href('inventories.edit.networking', {inventory_id: inventory.id, inventory_name: inventory.name});
|
||||||
.then(model => model.copy())
|
// window.open(url, '_blank');
|
||||||
.then(copy => $scope.editInventory(copy))
|
|
||||||
.catch(({ data, status }) => {
|
|
||||||
const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` };
|
|
||||||
ProcessErrors($scope, data, status, null, params);
|
|
||||||
})
|
|
||||||
.finally(() => Wait('stop'));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.editInventory = function (inventory) {
|
$scope.editInventory = function (inventory) {
|
||||||
|
|||||||
@@ -194,6 +194,9 @@ angular.module('GeneratorHelpers', [systemStatus.name])
|
|||||||
case 'insights':
|
case 'insights':
|
||||||
icon = "fa-info";
|
icon = "fa-info";
|
||||||
break;
|
break;
|
||||||
|
case 'network':
|
||||||
|
icon = "fa-sitemap";
|
||||||
|
break;
|
||||||
case 'cancel':
|
case 'cancel':
|
||||||
icon = "fa-minus-circle";
|
icon = "fa-minus-circle";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -61,3 +61,11 @@ require('ng-toast-provider');
|
|||||||
require('ng-toast-directives');
|
require('ng-toast-directives');
|
||||||
require('ng-toast');
|
require('ng-toast');
|
||||||
require('lr-infinite-scroll');
|
require('lr-infinite-scroll');
|
||||||
|
|
||||||
|
// Network Visualization
|
||||||
|
require('angular-mousewheel');
|
||||||
|
require('angular-xeditable');
|
||||||
|
require('hamsterjs');
|
||||||
|
require('titlecase');
|
||||||
|
require('inherits');
|
||||||
|
require('mathjs');
|
||||||
|
|||||||
@@ -124,6 +124,12 @@
|
|||||||
"reconnectingwebsocket": "^1.0.0",
|
"reconnectingwebsocket": "^1.0.0",
|
||||||
"rrule": "git+https://git@github.com/jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c",
|
"rrule": "git+https://git@github.com/jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c",
|
||||||
"select2": "^4.0.2",
|
"select2": "^4.0.2",
|
||||||
"sprintf-js": "^1.0.3"
|
"sprintf-js": "^1.0.3",
|
||||||
|
"mathjs": "^3.15.0",
|
||||||
|
"hamsterjs": "^1.1.2",
|
||||||
|
"titlecase": "^1.1.2",
|
||||||
|
"inherits": "^1.0.2",
|
||||||
|
"angular-mousewheel": "^1.0.5",
|
||||||
|
"angular-xeditable": "~0.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user