| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| /** |
| * @module QDR |
| */ |
| var QDR = (function (QDR) { |
| |
| |
| QDR.module.controller('QDR.TopologyFormController', function ($scope, QDRService) { |
| |
| $scope.attributes = [] |
| var nameTemplate = '<div title="{{row.entity.description}}" class="ngCellText"><span>{{row.entity.attributeName}}</span></div>'; |
| var valueTemplate = '<div title="{{row.entity.attributeValue}}" class="ngCellText"><span>{{row.entity.attributeValue}}</span></div>'; |
| $scope.topoGridOptions = { |
| data: 'attributes', |
| enableColumnResize: true, |
| multiSelect: false, |
| columnDefs: [ |
| { |
| field: 'attributeName', |
| displayName: 'Attribute', |
| cellTemplate: nameTemplate |
| }, |
| { |
| field: 'attributeValue', |
| displayName: 'Value', |
| cellTemplate: valueTemplate |
| } |
| ] |
| }; |
| $scope.form = '' |
| $scope.$on('showEntityForm', function (event, args) { |
| var attributes = args.attributes; |
| var entityTypes = QDRService.schema.entityTypes[args.entity].attributes; |
| attributes.forEach( function (attr) { |
| if (entityTypes[attr.attributeName] && entityTypes[attr.attributeName].description) |
| attr.description = entityTypes[attr.attributeName].description |
| }) |
| $scope.attributes = attributes; |
| $scope.form = args.entity; |
| }) |
| $scope.$on('showAddForm', function (event) { |
| $scope.form = 'add'; |
| }) |
| }) |
| |
| |
| /** |
| * @method TopologyController |
| * |
| * Controller that handles the QDR topology page |
| */ |
| QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$dialog', |
| function($scope, $rootScope, QDRService, $location, $timeout, $dialog) { |
| |
| $scope.multiData = [] |
| $scope.selectedClient = []; |
| $scope.quiesceState = {} |
| var dontHide = false; |
| $scope.quiesceConnection = function (row) { |
| var entity = row.entity; |
| var state = $scope.quiesceState[entity.connectionId].state; |
| if (state === 'enabled') { |
| // start quiescing all links |
| $scope.quiesceState[entity.connectionId].state = 'quiescing'; |
| } else if (state === 'quiesced') { |
| // start reviving all links |
| $scope.quiesceState[entity.connectionId].state = 'reviving'; |
| } |
| $scope.multiDetails.updateState(entity); |
| dontHide = true; |
| $scope.multiDetails.selectRow(row.rowIndex, true); |
| $scope.multiDetails.showLinksList(row) |
| } |
| $scope.quiesceDisabled = function (row) { |
| return $scope.quiesceState[row.entity.connectionId].buttonDisabled; |
| } |
| $scope.quiesceText = function (row) { |
| return $scope.quiesceState[row.entity.connectionId].buttonText; |
| } |
| $scope.quiesceClass = function (row) { |
| var stateClassMap = { |
| enabled: 'btn-primary', |
| quiescing: 'btn-warning', |
| reviving: 'btn-warning', |
| quiesced: 'btn-danger' |
| } |
| return stateClassMap[$scope.quiesceState[row.entity.connectionId].state]; |
| } |
| $scope.multiDetails = { |
| data: 'multiData', |
| selectedItems: $scope.selectedClient, |
| multiSelect: false, |
| afterSelectionChange: function (obj) { |
| if (obj.selected && obj.orig) { |
| var detailsDiv = d3.select('#link_details') |
| var isVis = detailsDiv.style('display') === 'block'; |
| if (!dontHide && isVis && $scope.connectionId === obj.entity.connectionId) { |
| hideLinkDetails(); |
| return; |
| } |
| dontHide = false; |
| $scope.multiDetails.showLinksList(obj) |
| } |
| }, |
| showLinksList: function (obj) { |
| $scope.linkData = obj.entity.linkData; |
| $scope.connectionId = obj.entity.connectionId; |
| var visibleLen = Math.min(obj.entity.linkData.length, 10) |
| var left = parseInt(d3.select('#multiple_details').style("left")) |
| var detailsDiv = d3.select('#link_details') |
| detailsDiv |
| .style({ |
| display: 'block', |
| opacity: 1, |
| left: (left + 20) + "px", |
| top: (mouseY + 20 + $(document).scrollTop()) + "px", |
| height: (visibleLen + 1) * 30 + "px", // +1 for the header row |
| 'overflow-y': obj.entity.linkData > 10 ? 'scroll' : 'hidden'}) |
| }, |
| updateState: function (entity) { |
| var state = $scope.quiesceState[entity.connectionId].state |
| |
| // count enabled and disabled links for this connection |
| var enabled = 0, disabled = 0; |
| entity.linkData.forEach ( function (link) { |
| if (link.adminStatus === 'enabled') |
| ++enabled; |
| if (link.adminStatus === 'disabled') |
| ++disabled; |
| }) |
| |
| var linkCount = entity.linkData.length; |
| // if state is quiescing and any links are enabled, button should say 'Quiescing' and be disabled |
| if (state === 'quiescing' && (enabled > 0)) { |
| $scope.quiesceState[entity.connectionId].buttonText = 'Quiescing'; |
| $scope.quiesceState[entity.connectionId].buttonDisabled = true; |
| } else |
| // if state is enabled and all links are disabled, button should say Revive and be enabled. set state to quisced |
| // if state is quiescing and all links are disabled, button should say 'Revive' and be enabled. set state to quiesced |
| if ((state === 'quiescing' || state === 'enabled') && (disabled === linkCount)) { |
| $scope.quiesceState[entity.connectionId].buttonText = 'Revive'; |
| $scope.quiesceState[entity.connectionId].buttonDisabled = false; |
| $scope.quiesceState[entity.connectionId].state = 'quiesced' |
| } else |
| // if state is reviving and any links are disabled, button should say 'Reviving' and be disabled |
| if (state === 'reviving' && (disabled > 0)) { |
| $scope.quiesceState[entity.connectionId].buttonText = 'Reviving'; |
| $scope.quiesceState[entity.connectionId].buttonDisabled = true; |
| } else |
| // if state is reviving or quiesced and all links are enabled, button should say 'Quiesce' and be enabled. set state to enabled |
| if ((state === 'reviving' || state === 'quiesced') && (enabled === linkCount)) { |
| $scope.quiesceState[entity.connectionId].buttonText = 'Quiesce'; |
| $scope.quiesceState[entity.connectionId].buttonDisabled = false; |
| $scope.quiesceState[entity.connectionId].state = 'enabled' |
| } |
| }, |
| columnDefs: [ |
| { |
| field: 'host', |
| cellTemplate: "titleCellTemplate.html", |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| displayName: 'Connection host' |
| }, |
| { |
| field: 'user', |
| cellTemplate: "titleCellTemplate.html", |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| displayName: 'User' |
| }, |
| { |
| field: 'properties', |
| cellTemplate: "titleCellTemplate.html", |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| displayName: 'Properties' |
| }/*, |
| { |
| cellClass: 'gridCellButton', |
| cellTemplate: '<button title="{{quiesceText(row)}} the links" type="button" ng-class="quiesceClass(row)" class="btn" ng-click="$event.stopPropagation();quiesceConnection(row)" ng-disabled="quiesceDisabled(row)">{{quiesceText(row)}}</button>' |
| }*/ |
| ] |
| }; |
| $scope.linkData = []; |
| $scope.quiesceLinkClass = function (row) { |
| var stateClassMap = { |
| enabled: 'btn-primary', |
| disabled: 'btn-danger' |
| } |
| return stateClassMap[row.entity.adminStatus] |
| //return stateClassMap[$scope.quiesceState[row.entity.connectionId].linkStates[row.entity.identity]]; |
| } |
| $scope.quiesceLink = function (row) { |
| var state = row.entity.adminStatus === 'enabled' ? 'disabled' : 'enabled'; |
| $scope.quiesceState[row.entity.connectionId].linkStates[row.entity.identity] = state; |
| } |
| $scope.quiesceLinkDisabled = function (row) { |
| return false; |
| } |
| $scope.quiesceLinkText = function (row) { |
| return row.entity.adminStatus === 'disabled' ? "Revive" : "Quiesce"; |
| } |
| $scope.linkDetails = { |
| data: 'linkData', |
| columnDefs: [ |
| { |
| field: 'adminStatus', |
| cellTemplate: "titleCellTemplate.html", |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| displayName: 'Admin stat' |
| }, |
| { |
| field: 'dir', |
| cellTemplate: "titleCellTemplate.html", |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| displayName: 'dir' |
| }, |
| { |
| field: 'owningAddr', |
| cellTemplate: "titleCellTemplate.html", |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| displayName: 'Address' |
| }, |
| { |
| field: 'deliveryCount', |
| displayName: 'Delivered', |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| cellClass: 'grid-values' |
| |
| }, |
| { |
| field: 'undeliveredCount', |
| displayName: 'Undelivered', |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| cellClass: 'grid-values' |
| }, |
| { |
| field: 'unsettledCount', |
| displayName: 'Unsettled', |
| headerCellTemplate: 'titleHeaderCellTemplate.html', |
| cellClass: 'grid-values' |
| }/*, |
| |
| { |
| cellClass: 'gridCellButton', |
| cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>' |
| }*/ |
| ] |
| } |
| |
| if (!QDRService.connected) { |
| // we are not connected. we probably got here from a bookmark or manual page reload |
| QDRService.redirectWhenConnected("topology"); |
| return; |
| } |
| // we are currently connected. setup a handler to get notified if we are ever disconnected |
| QDRService.addDisconnectAction( function () { |
| QDRService.redirectWhenConnected("topology"); |
| $scope.$apply(); |
| }) |
| |
| QDR.log.debug("started QDR.TopologyController with urlPrefix: " + $location.absUrl()); |
| var urlPrefix = $location.absUrl(); |
| |
| $scope.addingNode = { |
| step: 0, |
| hasLink: false, |
| trigger: '' |
| }; |
| |
| $scope.cancel = function () { |
| $scope.addingNode.step = 0; |
| } |
| $scope.editNewRouter = function () { |
| $scope.addingNode.trigger = 'editNode'; |
| } |
| |
| var NewRouterName = "__NEW__"; |
| // mouse event vars |
| var selected_node = null, |
| selected_link = null, |
| mousedown_link = null, |
| mousedown_node = null, |
| mouseup_node = null, |
| initial_mouse_down_position = null; |
| |
| $scope.schema = "Not connected"; |
| |
| $scope.modes = [ |
| {title: 'Topology view', name: 'Diagram', right: false}, |
| /* {title: 'Add a new router node', name: 'Add Router', right: true} */ |
| ]; |
| $scope.mode = "Diagram"; |
| $scope.contextNode = null; // node that is associated with the current context menu |
| |
| $scope.isModeActive = function (name) { |
| if ((name == 'Add Router' || name == 'Diagram') && $scope.addingNode.step > 0) |
| return true; |
| return ($scope.mode == name); |
| } |
| $scope.selectMode = function (name) { |
| if (name == "Add Router") { |
| name = 'Diagram'; |
| if ($scope.addingNode.step > 0) { |
| $scope.addingNode.step = 0; |
| } else { |
| // start adding node mode |
| $scope.addingNode.step = 1; |
| } |
| } else { |
| $scope.addingNode.step = 0; |
| } |
| |
| $scope.mode = name; |
| } |
| $scope.$watch(function () {return $scope.addingNode.step}, function (newValue, oldValue) { |
| if (newValue == 0 && oldValue != 0) { |
| // we are cancelling the add |
| |
| // find the New node |
| nodes.every(function (n, i) { |
| // for the placeholder node, the key will be __internal__ |
| if (QDRService.nameFromId(n.key) == '__internal__') { |
| var newLinks = links.filter(function (e, i) { |
| return e.source.id == n.id || e.target.id == n.id; |
| }) |
| // newLinks is an array of links to remove |
| newLinks.map(function (e) { |
| links.splice(links.indexOf(e), 1); |
| }) |
| // i is the index of the node to remove |
| nodes.splice(i, 1); |
| force.nodes(nodes).links(links).start(); |
| restart(false); |
| return false; // stop looping |
| } |
| return true; |
| }) |
| updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0); |
| |
| } else if (newValue > 0) { |
| // we are starting the add mode |
| $scope.$broadcast('showAddForm') |
| |
| resetMouseVars(); |
| selected_node = null; |
| selected_link = null; |
| // add a new node |
| var id = "amqp:/_topo/0/__internal__/$management"; |
| var x = radiusNormal * 4; |
| var y = x;; |
| if (newValue > 1) { // add at current mouse position |
| var offset = jQuery('#topology').offset(); |
| x = mouseX - offset.left + $(document).scrollLeft(); |
| y = mouseY - offset.top + $(document).scrollTop();; |
| } |
| NewRouterName = genNewName(); |
| nodes.push( aNode(id, NewRouterName, "inter-router", undefined, nodes.length, x, y, undefined, true) ); |
| force.nodes(nodes).links(links).start(); |
| restart(false); |
| } |
| |
| }) |
| $scope.isRight = function (mode) { |
| return mode.right; |
| } |
| |
| // for ng-grid that shows details for multiple consoles/clients |
| // generate unique name for router and containerName |
| var genNewName = function () { |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| var nameIndex = 1; |
| var newName = "R." + nameIndex; |
| |
| var names = []; |
| for (key in nodeInfo) { |
| var node = nodeInfo[key]; |
| var router = node['.router']; |
| var attrNames = router.attributeNames; |
| var name = QDRService.valFor(attrNames, router.results[0], 'routerId') |
| if (!name) |
| name = QDRService.valFor(attrNames, router.results[0], 'name') |
| names.push(name); |
| } |
| |
| while (names.indexOf(newName) >= 0) { |
| newName = "R." + nameIndex++; |
| } |
| return newName; |
| } |
| |
| $scope.$watch(function () {return $scope.addingNode.trigger}, function (newValue, oldValue) { |
| if (newValue == 'editNode') { |
| $scope.addingNode.trigger = ""; |
| editNode(); |
| } |
| }) |
| |
| function editNode() { |
| doAddDialog(NewRouterName); |
| }; |
| $scope.reverseLink = function () { |
| if (!mousedown_link) |
| return; |
| var d = mousedown_link; |
| var tmp = d.left; |
| d.left = d.right;; |
| d.right = tmp; |
| restart(false); |
| tick(); |
| } |
| $scope.removeLink = function () { |
| if (!mousedown_link) |
| return; |
| var d = mousedown_link; |
| links.every( function (l, i) { |
| if (l.source.id == d.source.id && l.target.id == d.target.id) { |
| links.splice(i, 1); |
| force.links(links).start(); |
| return false; // exit the 'every' loop |
| } |
| return true; |
| }); |
| restart(false); |
| tick(); |
| } |
| $scope.setFixed = function (b) { |
| if ($scope.contextNode) { |
| $scope.contextNode.fixed = b; |
| } |
| restart(); |
| } |
| $scope.isFixed = function () { |
| if (!$scope.contextNode) |
| return false; |
| return ($scope.contextNode.fixed & 0b1); |
| } |
| |
| var mouseX, mouseY; |
| // event handlers for popup context menu |
| $(document).mousemove(function (e) { |
| mouseX = e.clientX; |
| mouseY = e.clientY; |
| }); |
| $(document).mousemove(); |
| $(document).click(function (e) { |
| $scope.contextNode = null; |
| $(".contextMenu").fadeOut(200); |
| }); |
| |
| |
| // set up SVG for D3 |
| var width, height; |
| var tpdiv = $('#topology'); |
| var colors = {'inter-router': "#EAEAEA", 'normal': "#F0F000", 'on-demand': '#00F000'}; |
| var gap = 5; |
| var radii = {'inter-router': 25, 'normal': 15, 'on-demand': 15}; |
| var radius = 25; |
| var radiusNormal = 15; |
| width = tpdiv.width() - gap; |
| height = $('#main').height() - $('#topology').position().top - gap; |
| |
| var svg, lsvg; |
| var force; |
| var animate = false; // should the force graph organize itself when it is displayed |
| var path, circle; |
| var savedKeys = {}; |
| var dblckickPos = [0,0]; |
| |
| // set up initial nodes and links |
| // - nodes are known by 'id', not by index in array. |
| // - selected edges are indicated on the node (as a bold red circle). |
| // - links are always source < target; edge directions are set by 'left' and 'right'. |
| var nodes = []; |
| var links = []; |
| |
| var aNode = function (id, name, nodeType, nodeInfo, nodeIndex, x, y, resultIndex, fixed, properties) { |
| properties = properties || {}; |
| var routerId; |
| if (nodeInfo) { |
| var node = nodeInfo[id]; |
| if (node) { |
| var router = node['.router']; |
| routerId = QDRService.valFor(router.attributeNames, router.results[0], 'id') |
| if (!routerId) |
| routerId = QDRService.valFor(router.attributeNames, router.results[0], 'routerId') |
| } |
| } |
| return { key: id, |
| name: name, |
| nodeType: nodeType, |
| properties: properties, |
| routerId: routerId, |
| x: x, |
| y: y, |
| id: nodeIndex, |
| resultIndex: resultIndex, |
| fixed: fixed, |
| cls: name == NewRouterName ? 'temp' : '' |
| }; |
| }; |
| |
| |
| var initForm = function (attributes, results, entityType, formFields) { |
| |
| while(formFields.length > 0) { |
| // remove all existing attributes |
| formFields.pop(); |
| } |
| |
| for (var i=0; i<attributes.length; ++i) { |
| var name = attributes[i]; |
| var val = results[i]; |
| var desc = ""; |
| if (entityType.attributes[name]) |
| if (entityType.attributes[name].description) |
| desc = entityType.attributes[name].description; |
| |
| formFields.push({'attributeName': name, 'attributeValue': val, 'description': desc}); |
| } |
| } |
| |
| // initialize the nodes and links array from the QDRService.topology._nodeInfo object |
| var initForceGraph = function () { |
| //QDR.log.debug("initForceGraph called"); |
| nodes = []; |
| links = []; |
| |
| svg = d3.select('#topology') |
| .append('svg') |
| .attr("id", "SVG_ID") |
| .attr('width', width) |
| .attr('height', height) |
| .on("contextmenu", function(d) { |
| if (d3.event.defaultPrevented) |
| return; |
| d3.event.preventDefault(); |
| if ($scope.addingNode.step != 0) |
| return; |
| if (d3.select('#svg_context_menu').style('display') !== 'block') |
| $(document).click(); |
| d3.select('#svg_context_menu') |
| .style('left', (mouseX + $(document).scrollLeft()) + "px") |
| .style('top', (mouseY + $(document).scrollTop()) + "px") |
| .style('display', 'block'); |
| }) |
| .on('click', function (d) { |
| removeCrosssection() |
| }); |
| |
| $(document).keyup(function(e) { |
| if (e.keyCode === 27) { |
| removeCrosssection() |
| } |
| }); |
| |
| // the legend |
| lsvg = d3.select("#svg_legend") |
| .append('svg') |
| .attr('id', 'svglegend') |
| lsvg = lsvg.append('svg:g') |
| .attr('transform', 'translate('+(radii['inter-router']+2)+','+(radii['inter-router']+2)+')') |
| .selectAll('g'); |
| |
| // mouse event vars |
| selected_node = null; |
| selected_link = null; |
| mousedown_link = null; |
| mousedown_node = null; |
| mouseup_node = null; |
| |
| // initialize the list of nodes |
| var yInit = 10; |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| var nodeCount = Object.keys(nodeInfo).length; |
| for (var id in nodeInfo) { |
| var name = QDRService.nameFromId(id); |
| // if we have any new nodes, animate the force graph to position them |
| var position = angular.fromJson(localStorage[name]); |
| if (!angular.isDefined(position)) { |
| animate = true; |
| position = {x: width / 4 + ((width / 2)/nodeCount) * nodes.length, |
| y: 200 + yInit, |
| fixed: false}; |
| } |
| if (position.y > height) |
| position.y = 200 - yInit; |
| nodes.push( aNode(id, name, "inter-router", nodeInfo, nodes.length, position.x, position.y, undefined, position.fixed) ); |
| yInit *= -1; |
| //QDR.log.debug("adding node " + nodes.length-1); |
| } |
| |
| // initialize the list of links |
| var source = 0; |
| var client = 1; |
| for (var id in nodeInfo) { |
| var onode = nodeInfo[id]; |
| var conns = onode['.connection'].results; |
| var attrs = onode['.connection'].attributeNames; |
| var parent = getNodeIndex(QDRService.nameFromId(id)); |
| //QDR.log.debug("external client parent is " + parent); |
| var normalsParent = {console: undefined, client: undefined}; // 1st normal node for this parent |
| |
| for (var j = 0; j < conns.length; j++) { |
| var role = QDRService.valFor(attrs, conns[j], "role"); |
| var properties = QDRService.valFor(attrs, conns[j], "properties") || {}; |
| var dir = QDRService.valFor(attrs, conns[j], "dir"); |
| if (role == "inter-router") { |
| var connId = QDRService.valFor(attrs, conns[j], "container"); |
| var target = getContainerIndex(connId); |
| if (target >= 0) |
| getLink(source, target, dir); |
| } else if (role == "normal" || role == "on-demand") { |
| // not a router, but an external client |
| //QDR.log.debug("found an external client for " + id); |
| var name = QDRService.nameFromId(id) + "." + client; |
| //QDR.log.debug("external client name is " + name + " and the role is " + role); |
| |
| // if we have any new clients, animate the force graph to position them |
| var position = angular.fromJson(localStorage[name]); |
| if (!angular.isDefined(position)) { |
| animate = true; |
| position = {x: nodes[parent].x + 40 + Math.sin(Math.PI/2 * client), |
| y: nodes[parent].y + 40 + Math.cos(Math.PI/2 * client), |
| fixed: false}; |
| } |
| if (position.y > height) |
| position.y = nodes[parent].y + 40 + Math.cos(Math.PI/2 * client) |
| var node = aNode(id, name, role, nodeInfo, nodes.length, position.x, position.y, j, position.fixed, properties) |
| var nodeType = QDRService.isAConsole(properties, QDRService.valFor(attrs, conns[j], "identity"), role, node.key) |
| |
| if (role === 'normal') { |
| node.user = QDRService.valFor(attrs, conns[j], "user") |
| node.isEncrypted = QDRService.valFor(attrs, conns[j], "isEncrypted") |
| node.host = QDRService.valFor(attrs, conns[j], "host") |
| node.connectionId = QDRService.valFor(attrs, conns[j], "identity") |
| |
| if (!normalsParent[nodeType]) { |
| normalsParent[nodeType] = node; |
| nodes.push( node ); |
| node.normals = [node]; |
| // now add a link |
| getLink(parent, nodes.length-1, dir); |
| client++; |
| } else { |
| normalsParent[nodeType].normals.push(node) |
| } |
| } else { |
| nodes.push( node) |
| // now add a link |
| getLink(parent, nodes.length-1, dir); |
| client++; |
| } |
| } |
| } |
| source++; |
| } |
| |
| $scope.schema = QDRService.schema; |
| // init D3 force layout |
| force = d3.layout.force() |
| .nodes(nodes) |
| .links(links) |
| .size([width, height]) |
| .linkDistance(function(d) { return d.target.nodeType === 'inter-router' ? 150 : 65 }) |
| .charge(-1800) |
| .friction(.10) |
| .gravity(0.0001) |
| .on('tick', tick) |
| .start() |
| |
| svg.append("svg:defs").selectAll('marker') |
| .data(["end-arrow", "end-arrow-selected"]) // Different link/path types can be defined here |
| .enter().append("svg:marker") // This section adds in the arrows |
| .attr("id", String) |
| .attr("viewBox", "0 -5 10 10") |
| //.attr("refX", 25) |
| .attr("markerWidth", 4) |
| .attr("markerHeight", 4) |
| .attr("orient", "auto") |
| .append("svg:path") |
| .attr('d', 'M 0 -5 L 10 0 L 0 5 z') |
| |
| svg.append("svg:defs").selectAll('marker') |
| .data(["start-arrow", "start-arrow-selected"]) // Different link/path types can be defined here |
| .enter().append("svg:marker") // This section adds in the arrows |
| .attr("id", String) |
| .attr("viewBox", "0 -5 10 10") |
| .attr("refX", 5) |
| .attr("markerWidth", 4) |
| .attr("markerHeight", 4) |
| .attr("orient", "auto") |
| .append("svg:path") |
| .attr('d', 'M 10 -5 L 0 0 L 10 5 z'); |
| |
| // handles to link and node element groups |
| path = svg.append('svg:g').selectAll('path'), |
| circle = svg.append('svg:g').selectAll('g'); |
| |
| force.on('end', function() { |
| //QDR.log.debug("force end called"); |
| circle |
| .attr('cx', function(d) { |
| localStorage[d.name] = angular.toJson({x: d.x, y: d.y, fixed: d.fixed}); |
| return d.x; }); |
| }); |
| |
| // app starts here |
| restart(false); |
| force.start(); |
| setTimeout(function () { |
| updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0); |
| }, 10) |
| |
| } |
| |
| function updateForm (key, entity, resultIndex) { |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| var onode = nodeInfo[key] |
| if (onode) { |
| var nodeResults = onode['.' + entity].results[resultIndex] |
| var nodeAttributes = onode['.' + entity].attributeNames |
| var attributes = nodeResults.map( function (row, i) { |
| return { |
| attributeName: nodeAttributes[i], |
| attributeValue: row |
| } |
| }) |
| // sort by attributeName |
| attributes.sort( function (a, b) { return a.attributeName.localeCompare(b.attributeName) }) |
| |
| // move the Name first |
| var nameIndex = attributes.findIndex ( function (attr) { |
| return attr.attributeName === 'name' |
| }) |
| if (nameIndex >= 0) |
| attributes.splice(0, 0, attributes.splice(nameIndex, 1)[0]); |
| // get the list of ports this router is listening on |
| if (entity === 'router') { |
| var listeners = onode['.listener'].results; |
| var listenerAttributes = onode['.listener'].attributeNames; |
| var normals = listeners.filter ( function (listener) { |
| return QDRService.valFor( listenerAttributes, listener, 'role') === 'normal'; |
| }) |
| var ports = [] |
| normals.forEach (function (normalListener) { |
| ports.push(QDRService.valFor( listenerAttributes, normalListener, 'port')) |
| }) |
| // add as 2nd row |
| if (ports.length) |
| attributes.splice(1, 0, {attributeName: 'Listening on', attributeValue: ports, description: 'The port on which this router is listening for connections'}); |
| } |
| |
| $scope.$broadcast('showEntityForm', {entity: entity, attributes: attributes}) |
| } |
| if (!$scope.$$phase) $scope.$apply() |
| } |
| |
| function getContainerIndex(_id) { |
| var nodeIndex = 0; |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| for (var id in nodeInfo) { |
| var node = nodeInfo[id]['.router']; |
| // there should be only one router entity for each node, so using results[0] should be fine |
| if (QDRService.valFor( node.attributeNames, node.results[0], "id") === _id) |
| return nodeIndex; |
| if (QDRService.valFor( node.attributeNames, node.results[0], "routerId") === _id) |
| return nodeIndex; |
| nodeIndex++ |
| } |
| // there was no router.id that matched, check deprecated router.routerId |
| nodeIndex = 0; |
| for (var id in nodeInfo) { |
| var node = nodeInfo[id]['.container']; |
| if (node) { |
| if (QDRService.valFor ( node.attributeNames, node.results[0], "containerName") === _id) |
| return nodeIndex; |
| } |
| nodeIndex++ |
| } |
| //QDR.log.warn("unable to find containerIndex for " + _id); |
| return -1; |
| } |
| |
| function getNodeIndex (_id) { |
| var nodeIndex = 0; |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| for (var id in nodeInfo) { |
| if (QDRService.nameFromId(id) == _id) return nodeIndex; |
| nodeIndex++ |
| } |
| QDR.log.warn("unable to find nodeIndex for " + _id); |
| return -1; |
| } |
| |
| function getLink (_source, _target, dir, cls) { |
| for (var i=0; i < links.length; i++) { |
| var s = links[i].source, t = links[i].target; |
| if (typeof links[i].source == "object") { |
| s = s.id; |
| t = t.id; |
| } |
| if (s == _source && t == _target) { |
| return i; |
| } |
| // same link, just reversed |
| if (s == _target && t == _source) { |
| return -i; |
| } |
| } |
| |
| //QDR.log.debug("creating new link (" + (links.length) + ") between " + nodes[_source].name + " and " + nodes[_target].name); |
| var link = { |
| source: _source, |
| target: _target, |
| left: dir != "out", |
| right: dir == "out", |
| cls: cls |
| }; |
| return links.push(link) - 1; |
| } |
| |
| |
| function resetMouseVars() { |
| mousedown_node = null; |
| mouseup_node = null; |
| mousedown_link = null; |
| } |
| |
| // update force layout (called automatically each iteration) |
| function tick() { |
| // draw directed edges with proper padding from node centers |
| path.attr('d', function (d) { |
| //QDR.log.debug("in tick for d"); |
| //console.dump(d); |
| var dtx = Math.max(0, Math.min(width, d.target.x)), |
| dty = Math.max(0, Math.min(height, d.target.y)), |
| dsx = Math.max(0, Math.min(width, d.source.x)), |
| dsy = Math.max(0, Math.min(height, d.source.y)); |
| |
| var deltaX = dtx - dsx, |
| deltaY = dty - dsy, |
| dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY), |
| normX = deltaX / dist, |
| normY = deltaY / dist; |
| var sourcePadding, targetPadding; |
| if (d.target.nodeType == "inter-router") { |
| // right arrow left line start |
| sourcePadding = d.left ? radius + 8 : radius; |
| // left arrow right line start |
| targetPadding = d.right ? radius + 16 : radius; |
| } else { |
| sourcePadding = d.left ? radiusNormal + 18 : radiusNormal; |
| targetPadding = d.right ? radiusNormal + 16 : radiusNormal; |
| } |
| var sourceX = dsx + (sourcePadding * normX), |
| sourceY = dsy + (sourcePadding * normY), |
| targetX = dtx - (targetPadding * normX), |
| targetY = dty - (targetPadding * normY); |
| sourceX = Math.max(0, Math.min(width, sourceX)) |
| sourceY = Math.max(0, Math.min(width, sourceY)) |
| targetX = Math.max(0, Math.min(width, targetX)) |
| targetY = Math.max(0, Math.min(width, targetY)) |
| |
| return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY; |
| }); |
| |
| circle.attr('transform', function (d) { |
| d.x = Math.max(d.x, radiusNormal * 2); |
| d.y = Math.max(d.y, radiusNormal * 2); |
| d.x = Math.max(0, Math.min(width, d.x)) |
| d.y = Math.max(0, Math.min(height, d.y)) |
| return 'translate(' + d.x + ',' + d.y + ')'; |
| }); |
| if (!animate) { |
| animate = true; |
| force.stop(); |
| } |
| } |
| |
| // highlight the paths between the selected node and the hovered node |
| function findNextHopNode(from, d) { |
| // d is the node that the mouse is over |
| // from is the selected_node .... |
| if (!from) |
| return null; |
| |
| if (from == d) |
| return selected_node; |
| |
| //QDR.log.debug("finding nextHop from: " + from.name + " to " + d.name); |
| var sInfo = QDRService.topology.nodeInfo()[from.key]; |
| |
| if (!sInfo) { |
| QDR.log.warn("unable to find topology node info for " + from.key); |
| return null; |
| } |
| |
| // find the hovered name in the selected name's .router.node results |
| if (!sInfo['.router.node']) |
| return null; |
| var aAr = sInfo['.router.node'].attributeNames; |
| var vAr = sInfo['.router.node'].results; |
| for (var hIdx=0; hIdx<vAr.length; ++hIdx) { |
| var addrT = QDRService.valFor(aAr, vAr[hIdx], "id" ); |
| if (addrT == d.name) { |
| //QDR.log.debug("found " + d.name + " at " + hIdx); |
| var nextHop = QDRService.valFor(aAr, vAr[hIdx], "nextHop"); |
| //QDR.log.debug("nextHop was " + nextHop); |
| return (nextHop == null) ? nodeFor(addrT) : nodeFor(nextHop); |
| } |
| } |
| return null; |
| } |
| |
| function nodeFor(name) { |
| for (var i=0; i<nodes.length; ++i) { |
| if (nodes[i].name == name) |
| return nodes[i]; |
| } |
| return null; |
| } |
| |
| function linkFor(source, target) { |
| for (var i=0; i<links.length; ++i) { |
| if ((links[i].source == source) && (links[i].target == target)) |
| return links[i]; |
| if ((links[i].source == target) && (links[i].target == source)) |
| return links[i]; |
| } |
| // the selected node was a client/broker |
| //QDR.log.debug("failed to find a link between "); |
| //console.dump(source); |
| //QDR.log.debug(" and "); |
| //console.dump(target); |
| return null; |
| } |
| |
| function clearPopups() { |
| d3.select("#crosssection").style("display", "none"); |
| $('.hastip').empty(); |
| d3.select("#multiple_details").style("display", "none") |
| d3.select("#link_details").style("display", "none") |
| d3.select('#node_context_menu').style('display', 'none'); |
| |
| } |
| function removeCrosssection() { |
| setTimeout(function () { |
| d3.select("[id^=tooltipsy]").remove() |
| $('.hastip').empty(); |
| }, 1010); |
| d3.select("#crosssection svg g").transition() |
| .duration(1000) |
| .attr("transform", "translate("+(dblckickPos[0]-140) + "," + (dblckickPos[1]-100) + ") scale(0)") |
| .style("opacity", 0) |
| .each("end", function (d) { |
| d3.select("#crosssection svg").remove(); |
| d3.select("#crosssection").style("display","none"); |
| }); |
| d3.select("#multiple_details").transition() |
| .duration(500) |
| .style("opacity", 0) |
| .each("end", function (d) { |
| d3.select("#multiple_details").style("display", "none") |
| stopUpdateConnectionsGrid(); |
| }) |
| hideLinkDetails(); |
| } |
| function hideLinkDetails() { |
| d3.select("#link_details").transition() |
| .duration(500) |
| .style("opacity", 0) |
| .each("end", function (d) { |
| d3.select("#link_details").style("display", "none") |
| }) |
| } |
| |
| // takes the nodes and links array of objects and adds svg elements for everything that hasn't already |
| // been added |
| function restart(start) { |
| circle.call(force.drag); |
| |
| // path (link) group |
| path = path.data(links); |
| |
| // update existing links |
| path.classed('selected', function(d) { return d === selected_link; }) |
| .classed('highlighted', function(d) { return d.highlighted; } ) |
| .classed('temp', function(d) { return d.cls == 'temp'; } ) |
| .attr('marker-start', function(d) { |
| var sel = d===selected_link ? '-selected' : ''; |
| return d.left ? 'url('+urlPrefix+'#start-arrow' + sel + ')' : ''; }) |
| .attr('marker-end', function(d) { |
| var sel = d===selected_link ? '-selected' : ''; |
| return d.right ? 'url('+urlPrefix+'#end-arrow' + sel +')' : ''; }) |
| |
| |
| // add new links. if links[] is longer than the existing paths, add a new path for each new element |
| path.enter().append('svg:path') |
| .attr('class', 'link') |
| .attr('marker-start', function(d) { |
| var sel = d===selected_link ? '-selected' : ''; |
| return d.left ? 'url('+urlPrefix+'#start-arrow' + sel + ')' : ''; }) |
| .attr('marker-end', function(d) { |
| var sel = d===selected_link ? '-selected' : ''; |
| return d.right ? 'url('+urlPrefix+'#end-arrow' + sel + ')' : ''; }) |
| .classed('temp', function(d) { return d.cls == 'temp'; } ) |
| .on('mouseover', function (d) { |
| if($scope.addingNode.step > 0) { |
| if (d.cls == 'temp') { |
| d3.select(this).classed('over', true); |
| } |
| return; |
| } |
| //QDR.log.debug("showing connections form"); |
| var resultIndex = 0; // the connection to use |
| var left = d.left ? d.target : d.source; |
| // right is the node that the arrow points to, left is the other node |
| var right = d.left ? d.source : d.target; |
| var onode = QDRService.topology.nodeInfo()[left.key]; |
| // loop through all the connections for left, and find the one for right |
| if (!onode || !onode['.connection']) |
| return; |
| // update the info dialog for the link the mouse is over |
| if (!selected_node && !selected_link) { |
| for (resultIndex=0; resultIndex < onode['.connection'].results.length; ++resultIndex) { |
| var conn = onode['.connection'].results[resultIndex]; |
| /// find the connection whose container is the right's name |
| var name = QDRService.valFor(onode['.connection'].attributeNames, conn, "container"); |
| if (name == right.routerId) { |
| break; |
| } |
| } |
| // did not find connection. this is a connection to a non-interrouter node |
| if (resultIndex === onode['.connection'].results.length) { |
| // use the non-interrouter node's connection info |
| left = d.target; |
| resultIndex = left.resultIndex; |
| } |
| if (resultIndex) |
| updateForm(left.key, 'connection', resultIndex); |
| } |
| |
| mousedown_link = d; |
| selected_link = mousedown_link; |
| restart(); |
| }) |
| .on('mouseout', function (d) { |
| if($scope.addingNode.step > 0) { |
| if (d.cls == 'temp') { |
| d3.select(this).classed('over', false); |
| } |
| return; |
| } |
| //QDR.log.debug("showing connections form"); |
| selected_link = null; |
| restart(); |
| }) |
| .on("contextmenu", function(d) { |
| $(document).click(); |
| d3.event.preventDefault(); |
| if (d.cls !== "temp") |
| return; |
| |
| mousedown_link = d; |
| d3.select('#link_context_menu') |
| .style('left', (mouseX + $(document).scrollLeft()) + "px") |
| .style('top', (mouseY + $(document).scrollTop()) + "px") |
| .style('display', 'block'); |
| }) |
| .on("click", function (d) { |
| dblckickPos = d3.mouse(this); |
| QDR.log.debug("dblckickPos is [" + dblckickPos[0] + ", " + dblckickPos[1] + "]") |
| d3.event.stopPropagation(); |
| clearPopups(); |
| var diameter = 400; |
| var format = d3.format(",d"); |
| var pack = d3.layout.pack() |
| .size([diameter - 4, diameter - 4]) |
| .padding(-10) |
| .value(function(d) { return d.size; }); |
| |
| d3.select("#crosssection svg").remove(); |
| var svg = d3.select("#crosssection").append("svg") |
| .attr("width", diameter) |
| .attr("height", diameter) |
| var svgg = svg.append("g") |
| .attr("transform", "translate(2,2)"); |
| |
| var root = { |
| name: "links between " + d.source.name + " and " + d.target.name, |
| children: [] |
| } |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| var connections = nodeInfo[d.source.key]['.connection']; |
| var containerIndex = connections.attributeNames.indexOf('container'); |
| connections.results.some ( function (connection) { |
| if (connection[containerIndex] == d.target.routerId) { |
| root.attributeNames = connections.attributeNames; |
| root.obj = connection; |
| root.desc = "Connection"; |
| return true; // stop looping after 1 match |
| } |
| return false; |
| }) |
| |
| // find router.links where link.remoteContainer is d.source.name |
| var links = nodeInfo[d.source.key]['.router.link']; |
| var identityIndex = connections.attributeNames.indexOf('identity') |
| var roleIndex = connections.attributeNames.indexOf('role') |
| var connectionIdIndex = links.attributeNames.indexOf('connectionId'); |
| var linkTypeIndex = links.attributeNames.indexOf('linkType'); |
| var nameIndex = links.attributeNames.indexOf('name'); |
| var linkDirIndex = links.attributeNames.indexOf('linkDir'); |
| |
| if (roleIndex < 0 || identityIndex < 0 || connectionIdIndex < 0 |
| || linkTypeIndex < 0 || nameIndex < 0 || linkDirIndex < 0) |
| return; |
| links.results.forEach ( function (link) { |
| if (root.obj && link[connectionIdIndex] == root.obj[identityIndex] && link[linkTypeIndex] == root.obj[roleIndex]) |
| root.children.push ( |
| { name: "(" + link[linkDirIndex] + ") " + link[nameIndex], |
| size: 100, |
| obj: link, |
| desc: "Link", |
| attributeNames: links.attributeNames |
| }) |
| }) |
| if (root.children.length == 0) |
| return; |
| var node = svgg.datum(root).selectAll(".node") |
| .data(pack.nodes) |
| .enter().append("g") |
| .attr("class", function(d) { return d.children ? "parent node hastip" : "leaf node hastip"; }) |
| .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" + (!d.children ? "scale(0.9)" : ""); }) |
| .attr("title", function (d) { |
| var title = "<h4>" + d.desc + "</h4><table class='tiptable'><tbody>"; |
| if (d.attributeNames) |
| d.attributeNames.forEach( function (n, i) { |
| title += "<tr><td>" + n + "</td><td>"; |
| title += d.obj[i] != null ? d.obj[i] : ''; |
| title += '</td></tr>'; |
| }) |
| title += "</tbody></table>" |
| return title |
| }) |
| |
| node.append("circle") |
| .attr("r", function(d) { return d.r; }); |
| |
| // node.filter(function(d) { return !d.children; }).append("text") |
| node.append("text") |
| .attr("dy", function (d) { return d.children ? "-10em" : ".3em"}) |
| .style("text-anchor", "middle") |
| .text(function(d) { |
| return d.name.substring(0, d.r / 3); |
| }); |
| |
| $('.hastip').tooltipsy({ alignTo: 'cursor'}); |
| svgg.attr("transform", "translate("+(dblckickPos[0]-140) + "," + (dblckickPos[1]-100) + ") scale(0.01)") |
| d3.select("#crosssection").style("display","block"); |
| |
| svgg.transition().attr("transform", "translate(2,2) scale(1)") |
| }) |
| |
| |
| // remove old links |
| path.exit().remove(); |
| |
| |
| // circle (node) group |
| // nodes are known by id |
| circle = circle.data(nodes, function (d) { |
| return d.id; |
| }); |
| |
| // update existing nodes visual states |
| circle.selectAll('circle') |
| .classed('selected', function (d) { return (d === selected_node) }) |
| .classed('fixed', function (d) { return (d.fixed & 0b1) }) |
| |
| // add new circle nodes. if nodes[] is longer than the existing paths, add a new path for each new element |
| var g = circle.enter().append('svg:g') |
| .classed('multiple', function(d) { return (d.normals && d.normals.length > 1) } ) |
| |
| var appendCircle = function (g) { |
| // add new circles and set their attr/class/behavior |
| return g.append('svg:circle') |
| .attr('class', 'node') |
| .attr('r', function (d) { return radii[d.nodeType] } ) |
| .classed('fixed', function (d) {return d.fixed}) |
| .classed('temp', function(d) { return QDRService.nameFromId(d.key) == '__internal__'; } ) |
| .classed('normal', function(d) { return d.nodeType == 'normal' } ) |
| .classed('inter-router', function(d) { return d.nodeType == 'inter-router' } ) |
| .classed('on-demand', function(d) { return d.nodeType == 'on-demand' } ) |
| .classed('console', function(d) { return QDRService.isConsole(d) } ) |
| .classed('artemis', function(d) { return QDRService.isArtemis(d) } ) |
| .classed('qpid-cpp', function(d) { return QDRService.isQpid(d) } ) |
| .classed('client', function(d) { return d.nodeType === 'normal' && !d.properties.console_identifier } ) |
| } |
| appendCircle(g).on('mouseover', function (d) { |
| if ($scope.addingNode.step > 0) { |
| d3.select(this).attr('transform', 'scale(1.1)'); |
| return; |
| } |
| if (!selected_node) { |
| if (d.nodeType === 'inter-router') { |
| //QDR.log.debug("showing general form"); |
| updateForm(d.key, 'router', 0); |
| } else if (d.nodeType === 'normal' || d.nodeType === 'on-demand') { |
| //QDR.log.debug("showing connections form"); |
| updateForm(d.key, 'connection', d.resultIndex); |
| } |
| } |
| |
| if (d === mousedown_node) |
| return; |
| //if (d === selected_node) |
| // return; |
| // enlarge target node |
| d3.select(this).attr('transform', 'scale(1.1)'); |
| // highlight the next-hop route from the selected node to this node |
| mousedown_node = null; |
| |
| if (!selected_node) { |
| return; |
| } |
| setTimeout(nextHop, 1, selected_node, d); |
| }) |
| .on('mouseout', function (d) { |
| // unenlarge target node |
| d3.select(this).attr('transform', ''); |
| for (var i=0; i<links.length; ++i) { |
| links[i]['highlighted'] = false; |
| } |
| restart(); |
| }) |
| .on('mousedown', function (d) { |
| if (d3.event.button !== 0) { // ignore all but left button |
| return; |
| } |
| mousedown_node = d; |
| // mouse position relative to svg |
| initial_mouse_down_position = d3.mouse(this.parentElement.parentElement.parentElement).slice(); |
| }) |
| .on('mouseup', function (d) { |
| if (!mousedown_node) |
| return; |
| |
| selected_link = null; |
| // unenlarge target node |
| d3.select(this).attr('transform', ''); |
| |
| // check for drag |
| mouseup_node = d; |
| var mySvg = this.parentElement.parentElement.parentElement; |
| // if we dragged the node, make it fixed |
| var cur_mouse = d3.mouse(mySvg); |
| if (cur_mouse[0] != initial_mouse_down_position[0] || |
| cur_mouse[1] != initial_mouse_down_position[1]) { |
| console.log("mouse pos changed. making this node fixed") |
| d3.select(this).classed("fixed", d.fixed = true); |
| resetMouseVars(); |
| return; |
| } |
| |
| // we didn't drag, we just clicked on the node |
| if ($scope.addingNode.step > 0) { |
| if (d.nodeType !== 'inter-router') |
| return; |
| if (QDRService.nameFromId(d.key) == '__internal__') |
| return; |
| |
| // add a link from the clicked node to the new node |
| getLink(d.id, nodes.length-1, "in", "temp"); |
| $scope.addingNode.hasLink = true; |
| if (!$scope.$$phase) $scope.$apply() |
| // add new elements to the svg |
| force.links(links).start(); |
| restart(); |
| return; |
| |
| } |
| |
| // if this node was selected, unselect it |
| if (mousedown_node === selected_node) { |
| selected_node = null; |
| } |
| else { |
| if (d.nodeType !== 'normal' && d.nodeType !== 'on-demand') |
| selected_node = mousedown_node; |
| } |
| for (var i=0; i<links.length; ++i) { |
| links[i]['highlighted'] = false; |
| } |
| mousedown_node = null; |
| if (!$scope.$$phase) $scope.$apply() |
| restart(false); |
| |
| }) |
| .on("dblclick", function (d) { |
| if (d.fixed) { |
| d3.select(this).classed("fixed", d.fixed = false); |
| force.start(); // let the nodes move to a new position |
| } |
| if (QDRService.nameFromId(d.key) == '__internal__') { |
| editNode(); |
| if (!$scope.$$phase) $scope.$apply() |
| } |
| }) |
| .on("contextmenu", function(d) { |
| $(document).click(); |
| d3.event.preventDefault(); |
| $scope.contextNode = d; |
| if (!$scope.$$phase) $scope.$apply() // we just changed a scope valiable during an async event |
| d3.select('#node_context_menu') |
| .style('left', (mouseX + $(document).scrollLeft()) + "px") |
| .style('top', (mouseY + $(document).scrollTop()) + "px") |
| .style('display', 'block'); |
| |
| }) |
| .on("click", function (d) { |
| // clicked on a circle |
| clearPopups(); |
| if (!d.normals) { |
| // circle was a router or a broker |
| if ( QDRService.isArtemis(d) && Core.ConnectionName === 'Artemis' ) { |
| $location.path('/jmx/attributes?tab=artemis&con=Artemis') |
| } |
| return; |
| } |
| clickPos = d3.mouse(this); |
| d3.event.stopPropagation(); |
| startUpdateConnectionsGrid(d); |
| }) |
| |
| var appendContent = function (g) { |
| // show node IDs |
| g.append('svg:text') |
| .attr('x', 0) |
| .attr('y', function (d) { |
| var y = 6; |
| if (QDRService.isArtemis(d)) |
| y = 8; |
| else if (QDRService.isQpid(d)) |
| y = 9; |
| else if (d.nodeType === 'inter-router') |
| y = 4; |
| return y;}) |
| .attr('class', 'id') |
| .classed('console', function(d) { return QDRService.isConsole(d) } ) |
| .classed('normal', function(d) { return d.nodeType === 'normal' } ) |
| .classed('on-demand', function(d) { return d.nodeType === 'on-demand' } ) |
| .classed('artemis', function(d) { return QDRService.isArtemis(d) } ) |
| .classed('qpid-cpp', function(d) { return QDRService.isQpid(d) } ) |
| .text(function (d) { |
| if (QDRService.isConsole(d)) { |
| return '\uf108'; // icon-desktop for this console |
| } |
| if (QDRService.isArtemis(d)) { |
| return '\ue900' |
| } |
| if (QDRService.isQpid(d)) { |
| return '\ue901'; |
| } |
| if (d.nodeType === 'normal') |
| return '\uf109'; // icon-laptop for clients |
| return d.name.length>7 ? d.name.substr(0,6)+'...' : d.name; |
| }); |
| } |
| appendContent(g) |
| |
| var appendTitle = function (g) { |
| g.append("svg:title").text(function (d) { |
| var x = ''; |
| if (d.normals && d.normals.length > 1) |
| x = " x " + d.normals.length; |
| if (QDRService.isConsole(d)) { |
| return 'Dispatch console' + x |
| } |
| if (d.properties.product == 'qpid-cpp') { |
| return 'Broker - qpid-cpp' + x |
| } |
| if ( QDRService.isArtemis(d) ) { |
| return 'Broker - Artemis' + x |
| } |
| return d.nodeType == 'normal' ? 'client' + x : (d.nodeType == 'on-demand' ? 'broker' : 'Router ' + d.name) |
| }) |
| } |
| appendTitle(g); |
| |
| // remove old nodes |
| circle.exit().remove(); |
| |
| // add subcircles |
| svg.selectAll('.subcircle').remove(); |
| var multiples = svg.selectAll('.multiple') |
| multiples.each( function (d) { |
| d.normals.forEach( function (n, i) { |
| if (i<d.normals.length-1 && i<3) // only show a few shadow circles |
| this.insert('svg:circle', ":first-child") |
| .attr('class', 'subcircle node') |
| .attr('r', 15 - i) |
| .attr('transform', "translate("+ 4 * (i+1) +", 0)") |
| }, d3.select(this)) |
| }) |
| |
| // dynamically create the legend based on which node types are present |
| var legendNodes = []; |
| legendNodes.push(aNode("Router", "", "inter-router", undefined, 0, 0, 0, 0, false, {})) |
| |
| if (!svg.selectAll('circle.console').empty()) { |
| legendNodes.push(aNode("Dispatch console", "", "normal", undefined, 1, 0, 0, 0, false, {console_identifier: 'Dispatch console'})) |
| } |
| if (!svg.selectAll('circle.client').empty()) { |
| legendNodes.push(aNode("Client", "", "normal", undefined, 2, 0, 0, 0, false, {})) |
| } |
| if (!svg.selectAll('circle.qpid-cpp').empty()) { |
| legendNodes.push(aNode("Qpid cpp broker", "", "on-demand", undefined, 3, 0, 0, 0, false, {product: 'qpid-cpp'})) |
| } |
| if (!svg.selectAll('circle.artemis').empty()) { |
| legendNodes.push(aNode("Artemis broker", "", "on-demand", undefined, 4, 0, 0, 0, false, {})) |
| } |
| lsvg = lsvg.data(legendNodes, function (d) { |
| return d.id; |
| }); |
| var lg = lsvg.enter().append('svg:g') |
| .attr('transform', function (d, i) { |
| // 45px between lines and add 10px space after 1st line |
| return "translate(0, "+(45*i+(i>0?10:0))+")" |
| }) |
| appendCircle(lg) |
| appendContent(lg) |
| appendTitle(lg) |
| lg.append('svg:text') |
| .attr('x', 35) |
| .attr('y', 6) |
| .attr('class', "label") |
| .text(function (d) {return d.key }) |
| lsvg.exit().remove(); |
| var svgEl = document.getElementById("svglegend"), |
| bb = svgEl.getBBox(); |
| svgEl.style.height = (bb.y + bb.height) + 'px'; |
| svgEl.style.width = (bb.x + bb.width) + 'px'; |
| |
| if (!mousedown_node || !selected_node) |
| return; |
| |
| if (!start) |
| return; |
| // set the graph in motion |
| //QDR.log.debug("mousedown_node is " + mousedown_node); |
| force.start(); |
| |
| } |
| |
| var startUpdateConnectionsGrid = function (d) { |
| var extendConnections = function () { |
| $scope.multiData = [] |
| var normals = d.normals; |
| // find updated normals for d |
| d3.selectAll('.normal') |
| .each(function(newd) { |
| if (newd.id == d.id && newd.name == d.name) { |
| normals = newd.normals; |
| } |
| }); |
| if (normals) { |
| normals.forEach( function (n) { |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| var links = nodeInfo[n.key]['.router.link']; |
| var linkTypeIndex = links.attributeNames.indexOf('linkType'); |
| var connectionIdIndex = links.attributeNames.indexOf('connectionId'); |
| n.linkData = []; |
| links.results.forEach( function (link) { |
| if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.connectionId) { |
| var l = {}; |
| l.owningAddr = QDRService.valFor(links.attributeNames, link, 'owningAddr'); |
| l.dir = QDRService.valFor(links.attributeNames, link, 'linkDir'); |
| if (l.owningAddr && l.owningAddr.length > 2) |
| if (l.owningAddr[0] === 'M') |
| l.owningAddr = l.owningAddr.substr(2) |
| else |
| l.owningAddr = l.owningAddr.substr(1) |
| |
| l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount')); |
| l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount')); |
| l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount')); |
| l.adminStatus = QDRService.valFor(links.attributeNames, link, 'adminStatus'); |
| l.identity = QDRService.valFor(links.attributeNames, link, 'identity') |
| l.connectionId = QDRService.valFor(links.attributeNames, link, 'connectionId') |
| |
| // TODO: remove this fake quiescing/reviving logic when the routers do the work |
| initConnState(n.connectionId) |
| if ($scope.quiesceState[n.connectionId].linkStates[l.identity]) |
| l.adminStatus = $scope.quiesceState[n.connectionId].linkStates[l.identity]; |
| if ($scope.quiesceState[n.connectionId].state == 'quiescing') { |
| if (l.adminStatus === 'enabled') { |
| // 25% chance of switching |
| var chance = Math.floor(Math.random() * 2); |
| if (chance == 1) { |
| l.adminStatus = 'disabled'; |
| $scope.quiesceState[n.connectionId].linkStates[l.identity] = 'disabled'; |
| } |
| } |
| } |
| if ($scope.quiesceState[n.connectionId].state == 'reviving') { |
| if (l.adminStatus === 'disabled') { |
| // 25% chance of switching |
| var chance = Math.floor(Math.random() * 2); |
| if (chance == 1) { |
| l.adminStatus = 'enabled'; |
| $scope.quiesceState[n.connectionId].linkStates[l.identity] = 'enabled'; |
| } |
| } |
| } |
| QDR.log.debug("pushing link state for " + l.owningAddr + " status: "+ l.adminStatus) |
| |
| |
| n.linkData.push(l) |
| } |
| }) |
| $scope.multiData.push(n) |
| if (n.connectionId == $scope.connectionId) |
| $scope.linkData = n.linkData; |
| initConnState(n.connectionId) |
| $scope.multiDetails.updateState(n) |
| }) |
| } |
| $scope.$apply(); |
| |
| d3.select('#multiple_details') |
| .style({ |
| height: (normals.length + 1) * 30 + "px", |
| 'overflow-y': normals.length > 10 ? 'scroll' : 'hidden' |
| }) |
| } |
| |
| QDRService.addUpdatedAction("normalsStats", extendConnections) |
| extendConnections(); |
| clearPopups(); |
| var display = 'block' |
| var left = mouseX + $(document).scrollLeft() |
| if (d.normals.length === 1) { |
| display = 'none' |
| left = left - 30; |
| mouseY = mouseY - 20 |
| } |
| d3.select('#multiple_details') |
| .style({ |
| display: display, |
| opacity: 1, |
| left: (mouseX + $(document).scrollLeft()) + "px", |
| top: (mouseY + $(document).scrollTop()) + "px"}) |
| if (d.normals.length === 1) { |
| // simulate a click on the connection to popup the link details |
| $scope.multiDetails.showLinksList( {entity: d} ) |
| } |
| } |
| var stopUpdateConnectionsGrid = function () { |
| QDRService.delUpdatedAction("normalsStats"); |
| } |
| |
| var initConnState = function (id) { |
| if (!angular.isDefined($scope.quiesceState[id])) { |
| $scope.quiesceState[id] = { |
| state: 'enabled', |
| buttonText: 'Quiesce', |
| buttonDisabled: false, |
| linkStates: {} |
| } |
| } |
| } |
| |
| function nextHop(thisNode, d) { |
| if ((thisNode) && (thisNode != d)) { |
| var target = findNextHopNode(thisNode, d); |
| //QDR.log.debug("highlight link from node "); |
| //console.dump(nodeFor(selected_node.name)); |
| //console.dump(target); |
| if (target) { |
| var hlLink = linkFor(nodeFor(thisNode.name), target); |
| //QDR.log.debug("need to highlight"); |
| //console.dump(hlLink); |
| if (hlLink) |
| hlLink['highlighted'] = true; |
| else |
| target = null; |
| } |
| setTimeout(nextHop, 1, target, d); |
| } |
| restart(); |
| } |
| |
| |
| function mousedown() { |
| // prevent I-bar on drag |
| //d3.event.preventDefault(); |
| |
| // because :active only works in WebKit? |
| svg.classed('active', true); |
| } |
| |
| QDRService.addUpdatedAction("topology", function() { |
| //QDR.log.debug("Topology controller was notified that the model was updated"); |
| if (hasChanged()) { |
| QDR.log.info("svg graph changed") |
| saveChanged(); |
| // TODO: update graph nodes instead of rebuilding entire graph |
| d3.select("#SVG_ID").remove(); |
| d3.select("#svg_legend svg").remove(); |
| animate = true; |
| initForceGraph(); |
| //if ($location.path().startsWith("/topology")) |
| // Core.notification('info', "Qpid dispatch router topology changed"); |
| |
| } else { |
| //QDR.log.debug("no changes") |
| } |
| }); |
| |
| function hasChanged () { |
| // Don't update the underlying topology diagram if we are adding a new node. |
| // Once adding is completed, the topology will update automatically if it has changed |
| if ($scope.addingNode.step > 0) |
| return false; |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| if (Object.keys(nodeInfo).length != Object.keys(savedKeys).length) |
| return true; |
| for (var key in nodeInfo) { |
| // if this node isn't in the saved node list |
| if (!savedKeys.hasOwnProperty(key)) |
| return true; |
| // if the number of connections for this node chaanged |
| if (nodeInfo[key]['.connection'].results.length != savedKeys[key]) { |
| /* |
| QDR.log.debug("number of connections changed for " + key); |
| QDR.log.debug("QDRService.topology._nodeInfo[key]['.connection'].results.length"); |
| console.dump(QDRService.topology._nodeInfo[key]['.connection'].results.length); |
| QDR.log.debug("savedKeys[key]"); |
| console.dump(savedKeys[key]); |
| */ |
| return true; |
| } |
| } |
| return false; |
| }; |
| function saveChanged () { |
| savedKeys = {}; |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| // save the number of connections per node |
| for (var key in nodeInfo) { |
| savedKeys[key] = nodeInfo[key]['.connection'].results.length; |
| } |
| //QDR.log.debug("saving current keys"); |
| console.dump(savedKeys); |
| }; |
| // we are about to leave the page, save the node positions |
| $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { |
| //QDR.log.debug("locationChangeStart"); |
| nodes.forEach( function (d) { |
| localStorage[d.name] = angular.toJson({x: d.x, y: d.y, fixed: d.fixed}); |
| }); |
| $scope.addingNode.step = 0; |
| |
| }); |
| // When the DOM element is removed from the page, |
| // AngularJS will trigger the $destroy event on |
| // the scope |
| $scope.$on("$destroy", function( event ) { |
| //QDR.log.debug("scope on destroy"); |
| QDRService.stopUpdating(); |
| QDRService.delUpdatedAction("topology"); |
| d3.select("#SVG_ID").remove(); |
| }); |
| |
| initForceGraph(); |
| saveChanged(); |
| QDRService.startUpdating(); |
| |
| function doAddDialog(NewRouterName) { |
| var d = $dialog.dialog({ |
| dialogClass: "modal dlg-large", |
| backdrop: true, |
| keyboard: true, |
| backdropClick: true, |
| controller: 'QDR.NodeDialogController', |
| templateUrl: 'node-config-template.html', |
| resolve: { |
| newname: function () { |
| return NewRouterName; |
| } |
| } |
| }); |
| d.open().then(function (result) { |
| if (result) |
| doDownloadDialog(result); |
| }); |
| }; |
| |
| function doDownloadDialog(result) { |
| d = $dialog.dialog({ |
| backdrop: true, |
| keyboard: true, |
| backdropClick: true, |
| controller: 'QDR.DownloadDialogController', |
| templateUrl: 'download-dialog-template.html', |
| resolve: { |
| results: function () { |
| return result; |
| } |
| } |
| }); |
| d.open().then(function (result) { |
| //QDR.log.debug("download dialog done") |
| }) |
| if (!$scope.$$phase) $scope.$apply() |
| }; |
| }]); |
| |
| QDR.module.controller("QDR.NodeDialogController", function($scope, QDRService, dialog, newname) { |
| var schema = QDRService.schema; |
| var myEntities = ['router', 'log', 'listener' ]; |
| var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean'}; |
| var newLinks = $('path.temp').toArray(); // jquery array of new links for the added router |
| var nodeInfo = QDRService.topology.nodeInfo(); |
| var separatedEntities = []; // additional entities required if a link is reversed |
| var myPort = 0, myAddr = '0.0.0.0'; // port and address for new router |
| $scope.entities = []; |
| |
| // find max port number that is used in all the listeners |
| var getMaxPort = function (nodeInfo) { |
| var maxPort = 5674; |
| for (var key in nodeInfo) { |
| var node = nodeInfo[key]; |
| var listeners = node['.listener']; |
| var attrs = listeners.attributeNames; |
| for (var i=0; i<listeners.results.length; ++i) { |
| var res = listeners.results[i]; |
| var port = QDRService.valFor(attrs, res, 'port'); |
| if (parseInt(port, 10) > maxPort) |
| maxPort = parseInt(port, 10); |
| } |
| } |
| return maxPort; |
| } |
| var maxPort = getMaxPort(nodeInfo); |
| |
| // construct an object that contains all the info needed for a single tab's fields |
| var entity = function (actualName, tabName, humanName, ent, icon, link) { |
| var nameIndex = -1; // the index into attributes that the name field was placed |
| var index = 0; |
| var info = { |
| actualName: actualName, |
| tabName: tabName, |
| humanName: humanName, |
| description:ent.description, |
| icon: angular.isDefined(icon) ? icon : '', |
| references: ent.references, |
| link: link, |
| |
| attributes: $.map(ent.attributes, function (value, key) { |
| // skip identity and depricated fields |
| if (key == 'identity' || value.description.startsWith('Deprecated')) |
| return null; |
| var val = value['default']; |
| if (key == 'name') |
| nameIndex = index; |
| index++; |
| return { name: key, |
| humanName: QDRService.humanify(key), |
| description:value.description, |
| type: typeMap[value.type], |
| rawtype: value.type, |
| input: typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input' |
| : 'select', |
| selected: val ? val : undefined, |
| 'default': value['default'], |
| value: val, |
| required: value.required, |
| unique: value.unique |
| }; |
| }) |
| } |
| // move the 'name' attribute to the 1st position |
| if (nameIndex > -1) { |
| var tmp = info.attributes[0]; |
| info.attributes[0] = info.attributes[nameIndex]; |
| info.attributes[nameIndex] = tmp; |
| } |
| return info; |
| } |
| |
| // remove the annotation fields |
| var stripAnnotations = function (entityName, ent, annotations) { |
| if (ent.references) { |
| var newEnt = {attributes: {}}; |
| ent.references.forEach( function (annoKey) { |
| if (!annotations[annoKey]) |
| annotations[annoKey] = {}; |
| annotations[annoKey][entityName] = true; // create the key/consolidate duplicates |
| var keys = Object.keys(schema.annotations[annoKey].attributes); |
| for (var attrib in ent.attributes) { |
| if (keys.indexOf(attrib) == -1) { |
| newEnt.attributes[attrib] = ent.attributes[attrib]; |
| } |
| } |
| // add a field for the reference name |
| newEnt.attributes[annoKey] = {type: 'string', |
| description: 'Name of the ' + annoKey + ' section.', |
| 'default': annoKey, required: true}; |
| }) |
| newEnt.references = ent.references; |
| newEnt.description = ent.description; |
| return newEnt; |
| } |
| return ent; |
| } |
| |
| var annotations = {}; |
| myEntities.forEach(function (entityName) { |
| var ent = schema.entityTypes[entityName]; |
| var hName = QDRService.humanify(entityName); |
| if (entityName == 'listener') |
| hName = "Listener for clients"; |
| var noAnnotations = stripAnnotations(entityName, ent, annotations); |
| var ediv = entity(entityName, entityName, hName, noAnnotations, undefined); |
| if (ediv.actualName == 'router') { |
| ediv.attributes.filter(function (attr) { return attr.name == 'name'})[0].value = newname; |
| // if we have any new links (connectors), then the router's mode should be interior |
| if (newLinks.length) { |
| var roleAttr = ediv.attributes.filter(function (attr) { return attr.name == 'mode'})[0]; |
| roleAttr.value = roleAttr.selected = "interior"; |
| } |
| } |
| if (ediv.actualName == 'container') { |
| ediv.attributes.filter(function (attr) { return attr.name == 'containerName'})[0].value = newname + "-container"; |
| } |
| if (ediv.actualName == 'listener') { |
| // find max port number that is used in all the listeners |
| ediv.attributes.filter(function (attr) { return attr.name == 'port'})[0].value = ++maxPort; |
| } |
| // special case for required log.module since it doesn't have a default |
| if (ediv.actualName == 'log') { |
| var moduleAttr = ediv.attributes.filter(function (attr) { return attr.name == 'module'})[0]; |
| moduleAttr.value = moduleAttr.selected = "DEFAULT"; |
| } |
| $scope.entities.push( ediv ); |
| }) |
| |
| // add a tab for each annotation that was found |
| var annotationEnts = []; |
| for (var key in annotations) { |
| ent = angular.copy(schema.annotations[key]); |
| ent.attributes.name = {type: "string", unique: true, description: "Unique name that is used to refer to this set of attributes."} |
| var ediv = entity(key, key+'tab', QDRService.humanify(key), ent, undefined); |
| ediv.attributes.filter(function (attr) { return attr.name == 'name'})[0].value = key; |
| $scope.entities.push( ediv ); |
| annotationEnts.push( ediv ); |
| } |
| |
| // add an additional listener tab if any links are reversed |
| ent = schema.entityTypes['listener']; |
| newLinks.some(function (link) { |
| if (link.__data__.right) { |
| var noAnnotations = stripAnnotations('listener', ent, annotations); |
| var ediv = entity("listener", "listener0", "Listener (internal)", noAnnotations, undefined); |
| ediv.attributes.filter(function (attr) { return attr.name == 'port'})[0].value = ++maxPort; |
| // connectors from other routers need to connect to this addr:port |
| myPort = maxPort; |
| myAddr = ediv.attributes.filter(function (attr) { return attr.name == 'host'})[0].value |
| |
| // override the role. 'normal' is the default, but we want inter-router |
| ediv.attributes.filter(function( attr ) { return attr.name == 'role'})[0].selected = 'inter-router'; |
| separatedEntities.push( ediv ); |
| return true; // stop looping |
| } |
| return false; // continue looping |
| }) |
| |
| // Add connector tabs for each new link on the topology graph |
| ent = schema.entityTypes['connector']; |
| newLinks.forEach(function (link, i) { |
| var noAnnotations = stripAnnotations('connector', ent, annotations); |
| var ediv = entity('connector', 'connector' + i, " " + link.__data__.source.name, noAnnotations, link.__data__.right, link) |
| |
| // override the connector role. 'normal' is the default, but we want inter-router |
| ediv.attributes.filter(function( attr ) { return attr.name == 'role'})[0].selected = 'inter-router'; |
| |
| // find the addr:port of the inter-router listener to use |
| var listener = nodeInfo[link.__data__.source.key]['.listener']; |
| var attrs = listener.attributeNames; |
| for (var i=0; i<listener.results.length; ++i) { |
| var res = listener.results[i]; |
| var role = QDRService.valFor(attrs, res, 'role'); |
| if (role == 'inter-router') { |
| ediv.attributes.filter(function( attr ) { return attr.name == 'host'})[0].value = |
| QDRService.valFor(attrs, res, 'host') |
| ediv.attributes.filter(function( attr ) { return attr.name == 'port'})[0].value = |
| QDRService.valFor(attrs, res, 'port') |
| break; |
| } |
| } |
| if (link.__data__.right) { |
| // connectors from other nodes need to connect to the new router's listener addr:port |
| ediv.attributes.filter(function (attr) { return attr.name == 'port'})[0].value = myPort; |
| ediv.attributes.filter(function (attr) { return attr.name == 'host'})[0].value = myAddr; |
| |
| separatedEntities.push(ediv) |
| } |
| else |
| $scope.entities.push( ediv ); |
| }) |
| Array.prototype.push.apply($scope.entities, separatedEntities); |
| |
| // update the description on all the annotation tabs |
| annotationEnts.forEach ( function (ent) { |
| var shared = Object.keys(annotations[ent.actualName]); |
| ent.description += " These fields are shared by " + shared.join(" and ") + "."; |
| |
| }) |
| |
| $scope.testPattern = function (attr) { |
| if (attr.rawtype == 'path') |
| return /^(\/)?([^/\0]+(\/)?)+$/; |
| //return /^(.*\/)([^/]*)$/; |
| return /(.*?)/; |
| } |
| |
| $scope.attributeDescription = ''; |
| $scope.attributeType = ''; |
| $scope.attributeRequired = ''; |
| $scope.attributeUnique = ''; |
| $scope.active = 'router' |
| $scope.fieldsetDivs = "/fieldsetDivs.html" |
| $scope.setActive = function (tabName) { |
| $scope.active = tabName |
| } |
| $scope.isActive = function (tabName) { |
| return $scope.active === tabName |
| } |
| $scope.showDescription = function (attr, e) { |
| $scope.attributeDescription = attr.description; |
| var offset = jQuery(e.currentTarget).offset() |
| jQuery('.attr-description').offset({top: offset.top}) |
| |
| $scope.attributeType = "Type: " + JSON.stringify(attr.rawtype); |
| $scope.attributeRequired = attr.required ? 'required' : ''; |
| $scope.attributeUnique = attr.unique ? 'Must be unique' : ''; |
| } |
| // handle the download button click |
| // copy the dialog's values to the original node |
| $scope.download = function () { |
| dialog.close({entities: $scope.entities, annotations: annotations}); |
| } |
| $scope.cancel = function () { |
| dialog.close() |
| }; |
| |
| $scope.selectAnnotationTab = function (tabName) { |
| var tabs = $( "#tabs" ).tabs(); |
| tabs.tabs("select", tabName); |
| } |
| |
| var initTabs = function () { |
| var div = angular.element("#tabs"); |
| if (!div.width()) { |
| setTimeout(initTabs, 100); |
| return; |
| } |
| $( "#tabs" ) |
| .tabs() |
| .addClass('ui-tabs-vertical ui-helper-clearfix'); |
| } |
| // start the update loop |
| initTabs(); |
| |
| }); |
| |
| QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRService, $templateCache, $window, dialog, results) { |
| var result = results.entities; |
| var annotations = results.annotations; |
| var annotationKeys = Object.keys(annotations); |
| var annotationSections = {}; |
| |
| // use the router's name as the file name if present |
| $scope.newRouterName = 'router'; |
| result.forEach( function (e) { |
| if (e.actualName == 'router') { |
| e.attributes.forEach( function (a) { |
| if (a.name == 'name') { |
| $scope.newRouterName = a.value; |
| } |
| }) |
| } |
| }) |
| $scope.newRouterName = $scope.newRouterName + ".conf"; |
| |
| var template = $templateCache.get('config-file-header.html'); |
| $scope.verbose = true; |
| $scope.$watch('verbose', function (newVal) { |
| if (newVal !== undefined) { |
| // recreate output using current verbose setting |
| getOutput(); |
| } |
| }) |
| |
| var getOutput = function () { |
| $scope.output = template + '\n'; |
| $scope.parts = []; |
| var commentChar = '#' |
| result.forEach(function (entity) { |
| // don't output a section for annotations, they get flattened into the entities |
| var section = ""; |
| if (entity.icon) { |
| section += "##\n## Add to " + entity.link.__data__.source.name + "'s configuration file\n##\n"; |
| } |
| section += "##\n## " + QDRService.humanify(entity.actualName) + " - " + entity.description + "\n##\n"; |
| section += entity.actualName + " {\n"; |
| entity.attributes.forEach(function (attribute) { |
| if (attribute.input == 'select') |
| attribute.value = attribute.selected; |
| |
| // treat values with all spaces and empty strings as undefined |
| attribute.value = String(attribute.value).trim(); |
| if (attribute.value === 'undefined' || attribute.value === '') |
| attribute.value = undefined; |
| |
| if ($scope.verbose) { |
| commentChar = attribute.required || attribute.value != attribute['default'] ? ' ' : '#'; |
| if (!attribute.value) { |
| commentChar = '#'; |
| attribute.value = ''; |
| } |
| section += commentChar + " " |
| + attribute.name + ":" + Array(Math.max(20 - attribute.name.length, 1)).join(" ") |
| + attribute.value |
| + Array(Math.max(20 - ((attribute.value)+"").length, 1)).join(" ") |
| + '# ' + attribute.description |
| + "\n"; |
| } else { |
| if (attribute.value) { |
| if (attribute.value != attribute['default'] || attribute.required) |
| section += " " |
| + attribute.name + ":" + Array(20 - attribute.name.length).join(" ") |
| + attribute.value + "\n"; |
| |
| } |
| } |
| }) |
| section += "}\n\n"; |
| // if entity.icon is true, this is a connector intended for another router |
| if (entity.icon) |
| $scope.parts.push({output: section, |
| link: entity.link, |
| name: entity.link.__data__.source.name, |
| references: entity.references}); |
| else |
| $scope.output += section; |
| |
| // if this section is actually an annotation |
| if (annotationKeys.indexOf(entity.actualName) > -1) { |
| annotationSections[entity.actualName] = section; |
| } |
| }) |
| // go back and add annotation sections to the parts |
| $scope.parts.forEach (function (part) { |
| for (var section in annotationSections) { |
| if (part.references.indexOf(section) > -1) { |
| part.output += annotationSections[section]; |
| } |
| } |
| }) |
| QDR.log.debug($scope.output); |
| } |
| |
| // handle the download button click |
| $scope.download = function () { |
| var output = $scope.output + "\n\n" |
| var blob = new Blob([output], { type: 'text/plain;charset=utf-16' }); |
| saveAs(blob, $scope.newRouterName); |
| } |
| |
| $scope.downloadPart = function (part) { |
| var linkName = part.link.__data__.source.name + 'additional.conf'; |
| var blob = new Blob([part.output], { type: 'text/plain;charset=utf-16' }); |
| saveAs(blob, linkName); |
| } |
| |
| $scope.done = function () { |
| dialog.close(); |
| } |
| }); |
| |
| return QDR; |
| }(QDR || {})); |