diff --git a/awx/network_ui/static/network_ui/Makefile b/awx/network_ui/static/network_ui/Makefile index 78b3c0c90c..a0f5e939fc 100644 --- a/awx/network_ui/static/network_ui/Makefile +++ b/awx/network_ui/static/network_ui/Makefile @@ -29,7 +29,7 @@ istanbul: simple-server: lint main lessc - python -m SimpleHTTPServer 8080 + python -m SimpleHTTPServer 8081 deploy: main diff --git a/awx/network_ui/static/network_ui/src/network.ui.controller.js b/awx/network_ui/static/network_ui/src/network.ui.controller.js index e3aeeb6ead..342a873285 100644 --- a/awx/network_ui/static/network_ui/src/network.ui.controller.js +++ b/awx/network_ui/static/network_ui/src/network.ui.controller.js @@ -17,7 +17,7 @@ var time = require('./time.js'); var util = require('./util.js'); var models = require('./models.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 NetworkUIController = function($scope, $document, $location, $window, $http) { @@ -26,7 +26,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http) var i = 0; $scope.api_token = ''; - $scope.disconnected = false; + $scope.disconnected = true; $scope.topology_id = $location.search().topology_id || 0; // 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.cursor = {'x':100, 'y': 100, 'hidden': false}; - $scope.debug = {'hidden': true}; + $scope.debug = {'hidden': false}; $scope.hide_buttons = false; $scope.hide_links = false; $scope.hide_interfaces = false; $scope.hide_groups = false; $scope.graph = {'width': window.innerWidth, - 'right_column': window.innerWidth - 300, + 'right_column': 300, 'height': window.innerHeight}; $scope.device_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() { + 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 + ')'); }; @@ -337,7 +350,13 @@ var NetworkUIController = function($scope, $document, $location, $window, $http) // Event Handlers + $scope.normalize_mouse_event = function ($event) { + $event.x = $event.pageX; + $event.y = $event.pageY; + }; + $scope.onMouseDown = function ($event) { + $scope.normalize_mouse_event($event); if ($scope.recording) { $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.normalize_mouse_event($event); if ($scope.recording) { $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.normalize_mouse_event($event); if ($scope.recording) { $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.normalize_mouse_event($event); if ($scope.recording) { $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.normalize_mouse_event($event); if ($scope.recording) { $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.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) { $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(){ $scope.graph.width = $window.innerWidth; - $scope.graph.right_column = $window.innerWidth - 300; + $scope.graph.right_column = 300; $scope.graph.height = $window.innerHeight; $scope.update_size(); diff --git a/awx/network_ui/static/network_ui/src/ngTouch.js b/awx/network_ui/static/network_ui/src/ngTouch.js new file mode 100644 index 0000000000..91da5c1c03 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/ngTouch.js @@ -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); + } + } + + }] + }; +}); diff --git a/awx/network_ui/static/network_ui/src/svg-crowbar.js b/awx/network_ui/static/network_ui/src/svg-crowbar.js new file mode 100644 index 0000000000..bba3093801 --- /dev/null +++ b/awx/network_ui/static/network_ui/src/svg-crowbar.js @@ -0,0 +1,250 @@ +/** + * @license svg-crowbar + * (c) 2013 The New York Times + * License: MIT + */ +function svg_crowbar () { + var doctype = ''; + + 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('', ''); + 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; diff --git a/awx/network_ui/static/network_ui/src/tower.app.js b/awx/network_ui/static/network_ui/src/tower.app.js index 6f648783df..c3eadda832 100644 --- a/awx/network_ui/static/network_ui/src/tower.app.js +++ b/awx/network_ui/static/network_ui/src/tower.app.js @@ -1,6 +1,5 @@ var angular = require('angular'); -var ui_router = require('angular-ui-router'); var tower = angular.module('tower', ['tablesUI', 'networkUI', 'ui.router']); @@ -31,5 +30,4 @@ tower.config(function($stateProvider, $urlRouterProvider) { }); exports.tower = tower; -exports.ui_router = ui_router; diff --git a/awx/ui/client/features/_index.less b/awx/ui/client/features/_index.less index e2339dc9e4..053f270ddd 100644 --- a/awx/ui/client/features/_index.less +++ b/awx/ui/client/features/_index.less @@ -1,2 +1,3 @@ @import 'credentials/_index'; @import 'users/tokens/_index'; +@import 'networking/_index'; diff --git a/awx/ui/client/features/index.js b/awx/ui/client/features/index.js index 01216e575f..5eb1cd96b2 100644 --- a/awx/ui/client/features/index.js +++ b/awx/ui/client/features/index.js @@ -6,6 +6,7 @@ import atFeaturesApplications from '~features/applications'; import atFeaturesCredentials from '~features/credentials'; import atFeaturesTemplates from '~features/templates'; import atFeaturesUsers from '~features/users'; +import atFeaturesNetworking from '~features/networking'; const MODULE_NAME = 'at.features'; @@ -16,7 +17,8 @@ angular.module(MODULE_NAME, [ atFeaturesApplications, atFeaturesCredentials, atFeaturesTemplates, - atFeaturesUsers + atFeaturesUsers, + atFeaturesNetworking ]); export default MODULE_NAME; diff --git a/awx/ui/client/features/networking/_index.less b/awx/ui/client/features/networking/_index.less new file mode 100644 index 0000000000..4abeb3f996 --- /dev/null +++ b/awx/ui/client/features/networking/_index.less @@ -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; +} diff --git a/awx/ui/client/features/networking/index.js b/awx/ui/client/features/networking/index.js new file mode 100644 index 0000000000..71c20257e9 --- /dev/null +++ b/awx/ui/client/features/networking/index.js @@ -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; diff --git a/awx/ui/client/features/networking/networking.controller.js b/awx/ui/client/features/networking/networking.controller.js new file mode 100644 index 0000000000..b1d76f815a --- /dev/null +++ b/awx/ui/client/features/networking/networking.controller.js @@ -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; diff --git a/awx/ui/client/features/networking/networking.strings.js b/awx/ui/client/features/networking/networking.strings.js new file mode 100644 index 0000000000..4aa4efca5e --- /dev/null +++ b/awx/ui/client/features/networking/networking.strings.js @@ -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; diff --git a/awx/ui/client/features/networking/networking.view.html b/awx/ui/client/features/networking/networking.view.html new file mode 100644 index 0000000000..32c74c738c --- /dev/null +++ b/awx/ui/client/features/networking/networking.view.html @@ -0,0 +1,36 @@ +
+
{{vm.panelTitle}}
+
+
+ + +
+
+ +
+
+
+
+
+ +
+
+ VIEW LAYERS +
+
diff --git a/awx/ui/client/index.template.ejs b/awx/ui/client/index.template.ejs index 0301d42da0..58ee4d85a0 100644 --- a/awx/ui/client/index.template.ejs +++ b/awx/ui/client/index.template.ejs @@ -16,6 +16,7 @@ +
diff --git a/awx/ui/client/lib/theme/index.less b/awx/ui/client/lib/theme/index.less index b5ac04dfca..04fbf92fc2 100644 --- a/awx/ui/client/lib/theme/index.less +++ b/awx/ui/client/lib/theme/index.less @@ -170,3 +170,9 @@ * the transition. */ @import '_resets'; + +/** + * Network Visualization Style + * + */ +@import '../../src/network_ui/style.less'; diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index ab93141b7d..519809e584 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -47,6 +47,8 @@ import atLibComponents from '~components'; import atLibModels from '~models'; import atLibServices from '~services'; +import network_ui from './network_ui/main'; + start.bootstrap(() => { angular.bootstrap(document.body, ['awApp']); }); @@ -97,6 +99,8 @@ angular users.name, projects.name, scheduler.name, + instanceGroups.name, + network_ui.tower.name, 'Utilities', 'templates', diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js index f90b953ab6..0ca86a2b3f 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js +++ b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js @@ -93,6 +93,13 @@ export default ['i18n', function(i18n) { fieldActions: { 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: { label: i18n._('Edit'), ngClick: 'editInventory(inventory)', diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js index 7b22a22dba..5410f1f54c 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js @@ -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}})`; } - $scope.copyInventory = inventory => { - Wait('start'); - new Inventory('get', inventory.id) - .then(model => model.copy()) - .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.goToGraph = function(inventory){ + $state.go('inventories.edit.networking', {inventory_id: inventory.id, inventory_name: inventory.name}); + // let url = $state.href('inventories.edit.networking', {inventory_id: inventory.id, inventory_name: inventory.name}); + // window.open(url, '_blank'); }; $scope.editInventory = function (inventory) { diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js index 9792f37ecc..f01b7110af 100644 --- a/awx/ui/client/src/shared/generator-helpers.js +++ b/awx/ui/client/src/shared/generator-helpers.js @@ -194,6 +194,9 @@ angular.module('GeneratorHelpers', [systemStatus.name]) case 'insights': icon = "fa-info"; break; + case 'network': + icon = "fa-sitemap"; + break; case 'cancel': icon = "fa-minus-circle"; break; diff --git a/awx/ui/client/src/vendor.js b/awx/ui/client/src/vendor.js index 4b58d5953a..7ad8681b41 100644 --- a/awx/ui/client/src/vendor.js +++ b/awx/ui/client/src/vendor.js @@ -61,3 +61,11 @@ require('ng-toast-provider'); require('ng-toast-directives'); require('ng-toast'); require('lr-infinite-scroll'); + +// Network Visualization +require('angular-mousewheel'); +require('angular-xeditable'); +require('hamsterjs'); +require('titlecase'); +require('inherits'); +require('mathjs'); diff --git a/awx/ui/package.json b/awx/ui/package.json index 335d033aa5..787aae80f9 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -124,6 +124,12 @@ "reconnectingwebsocket": "^1.0.0", "rrule": "git+https://git@github.com/jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", "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" } }