You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2018/05/17 16:09:26 UTC
[1/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js
files that were in the npm repository
Repository: qpid-dispatch
Updated Branches:
refs/heads/master 94e55a812 -> dfd3cf7cb
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrTopology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrTopology.js b/console/stand-alone/plugin/js/qdrTopology.js
new file mode 100644
index 0000000..cd0cdf2
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrTopology.js
@@ -0,0 +1,2098 @@
+/*
+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.
+*/
+'use strict';
+
+/* global angular d3 */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ QDR.module.controller('QDR.TopologyFormController', function($scope, $timeout) {
+
+ $scope.attributes = [];
+ $scope.attributesConnections = [];
+
+ $scope.form = 'Router';
+ $scope.$on('showEntityForm', function(event, args) {
+ let attributes = args.attributes;
+ // capitalize 1st letter
+ $scope.form = args.entity.charAt(0).toUpperCase() + args.entity.slice(1);
+
+ let H = '<div class="infoGrid">';
+ attributes.forEach( function (a, i) {
+ let even = (i % 2) ? 'even' : 'odd';
+ if (a.attributeName === 'Listening on')
+ even += ' listening-on';
+ H += ('<div class="'+ even +'"><span title="'+a.attributeName+'">' + a.attributeName + '</span><span title="'+a.attributeValue+'">' + a.attributeValue + '</span></div>');
+ });
+ H += '</div>';
+ $('#formInfo').html(H);
+
+ if (!$scope.$$phase) $scope.$apply();
+
+ });
+ $scope.infoStyle = function () {
+ return {
+ height: (Math.max($scope.attributes.length, 15) * 30 + 46) + 'px'
+ };
+ };
+
+ $scope.panelVisible = true; // show/hide the panel on the left
+ $scope.hideLeftPane = function () {
+ d3.select('.page-menu')
+ .style('left' , '-360px')
+ .style('z-index', '1');
+
+ d3.select('.diagram')
+ .transition().duration(300).ease('sin-in')
+ .style('margin-left', '-360px')
+ .each('end', function () {
+ $timeout(function () {$scope.panelVisible = false;});
+ let div = d3.select(this);
+ div.style('margin-left', '0');
+ div.style('padding-left', 0);
+ });
+ };
+ $scope.showLeftPane = function () {
+ d3.select('.page-menu')
+ .style('left' , '0px');
+
+ $timeout(function () {$scope.panelVisible = true;});
+ d3.select('.diagram')
+ .style('margin-left', '0px')
+ .transition().duration(300).ease('sin-out')
+ .style('margin-left', '300px')
+ .each('end', function () {
+ let div = d3.select(this);
+ div.style('margin-left', '0');
+ div.style('padding-left', '300px');
+ });
+ };
+
+
+ });
+ /**
+ * @method TopologyController
+ *
+ * Controller that handles the QDR topology page
+ */
+ QDR.module.controller('QDR.TopologyController', ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$uibModal', '$sce',
+ function($scope, $rootScope, QDRService, $location, $timeout, $uibModal, $sce) {
+
+ $scope.multiData = [];
+ $scope.quiesceState = {};
+ let dontHide = false;
+ $scope.crosshtml = $sce.trustAsHtml('');
+
+ $scope.quiesceConnection = function(row) {
+ let entity = row.entity;
+ let 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) {
+ const stateClassMap = {
+ enabled: 'btn-primary',
+ quiescing: 'btn-warning',
+ reviving: 'btn-warning',
+ quiesced: 'btn-danger'
+ };
+ return stateClassMap[$scope.quiesceState[row.entity.connectionId].state];
+ };
+
+ // This is the grid that shows each connection when a client node that represents multiple connections is clicked
+ $scope.multiData = [];
+ $scope.multiDetails = {
+ data: 'multiData',
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ jqueryUIDraggable: true,
+ enablePaging: false,
+ multiSelect: false,
+ enableSelectAll: false,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ noUnselect: true,
+ onRegisterApi: function (gridApi) {
+ if (gridApi.selection) {
+ gridApi.selection.on.rowSelectionChanged($scope, function(row){
+ let detailsDiv = d3.select('#link_details');
+ let isVis = detailsDiv.style('display') === 'block';
+ if (!dontHide && isVis && $scope.connectionId === row.entity.connectionId) {
+ hideLinkDetails();
+ return;
+ }
+ dontHide = false;
+ $scope.multiDetails.showLinksList(row);
+ });
+ }
+ },
+ showLinksList: function(obj) {
+ $scope.linkData = obj.entity.linkData;
+ $scope.connectionId = obj.entity.connectionId;
+ let visibleLen = Math.min(obj.entity.linkData.length, 10);
+ //QDR.log.debug("visibleLen is " + visibleLen)
+ let left = parseInt(d3.select('#multiple_details').style('left'), 10);
+ let offset = $('#topology').offset();
+ let detailsDiv = d3.select('#link_details');
+ detailsDiv
+ .style({
+ display: 'block',
+ opacity: 1,
+ left: (left + 20) + 'px',
+ top: (mouseY - offset.top + 20 + $(document).scrollTop()) + 'px',
+ height: ((visibleLen + 1) * 30) + 40 + 'px', // +1 for the header row
+ 'overflow-y': obj.entity.linkData > 10 ? 'scroll' : 'hidden'
+ });
+ },
+ updateState: function(entity) {
+ let state = $scope.quiesceState[entity.connectionId].state;
+
+ // count enabled and disabled links for this connection
+ let enabled = 0,
+ disabled = 0;
+ entity.linkData.forEach(function(link) {
+ if (link.adminStatus === 'enabled')
+ ++enabled;
+ if (link.adminStatus === 'disabled')
+ ++disabled;
+ });
+
+ let 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.quiesceLinkClass = function(row) {
+ const stateClassMap = {
+ enabled: 'btn-primary',
+ disabled: 'btn-danger'
+ };
+ return stateClassMap[row.entity.adminStatus];
+ };
+ $scope.quiesceLink = function(row) {
+ QDRService.management.topology.quiesceLink(row.entity.nodeId, row.entity.name)
+ .then( function (results, context) {
+ let statusCode = context.message.application_properties.statusCode;
+ if (statusCode < 200 || statusCode >= 300) {
+ QDR.Core.notification('error', context.message.statusDescription);
+ QDR.log.info('Error ' + context.message.statusDescription);
+ }
+ });
+ };
+ $scope.quiesceLinkDisabled = function(row) {
+ return (row.entity.operStatus !== 'up' && row.entity.operStatus !== 'down');
+ };
+ $scope.quiesceLinkText = function(row) {
+ return row.entity.operStatus === 'down' ? 'Revive' : 'Quiesce';
+ };
+ $scope.linkData = [];
+ $scope.linkDetails = {
+ data: 'linkData',
+ jqueryUIDraggable: true,
+ columnDefs: [{
+ field: 'adminStatus',
+ cellTemplate: 'titleCellTemplate.html',
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ displayName: 'Admin state'
+ }, {
+ field: 'operStatus',
+ cellTemplate: 'titleCellTemplate.html',
+ headerCellTemplate: 'titleHeaderCellTemplate.html',
+ displayName: 'Oper state'
+ }, {
+ 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: 'uncounts',
+ displayName: 'Outstanding',
+ 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>'
+ }*/
+ ]
+ };
+
+ let urlPrefix = $location.absUrl();
+ urlPrefix = urlPrefix.split('#')[0];
+ QDR.log.debug('started QDR.TopologyController with urlPrefix: ' + urlPrefix);
+
+ // mouse event vars
+ let selected_node = null,
+ selected_link = null,
+ mousedown_link = null,
+ mousedown_node = null,
+ mouseover_node = null,
+ mouseup_node = null,
+ initial_mouse_down_position = null;
+
+ $scope.schema = 'Not connected';
+
+ $scope.contextNode = null; // node that is associated with the current context menu
+ $scope.isRight = function(mode) {
+ return mode.right;
+ };
+
+ var setNodesFixed = function (name, b) {
+ nodes.some(function (n) {
+ if (n.name === name) {
+ n.fixed = b;
+ return true;
+ }
+ });
+ };
+ $scope.setFixed = function(b) {
+ if ($scope.contextNode) {
+ $scope.contextNode.fixed = b;
+ setNodesFixed($scope.contextNode.name, b);
+ savePositions();
+ }
+ restart();
+ };
+ $scope.isFixed = function() {
+ if (!$scope.contextNode)
+ return false;
+ return ($scope.contextNode.fixed & 1);
+ };
+
+ let mouseX, mouseY;
+ var relativeMouse = function () {
+ let offset = $('#main_container').offset();
+ return {left: (mouseX + $(document).scrollLeft()) - 1,
+ top: (mouseY + $(document).scrollTop()) - 1,
+ offset: offset
+ };
+ };
+ // event handlers for popup context menu
+ $(document).mousemove(function(e) {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ });
+ $(document).mousemove();
+ $(document).click(function() {
+ $scope.contextNode = null;
+ $('.contextMenu').fadeOut(200);
+ });
+
+ const radii = {
+ 'inter-router': 25,
+ 'normal': 15,
+ 'on-demand': 15,
+ 'route-container': 15,
+ };
+ let radius = 25;
+ let radiusNormal = 15;
+ let svg, lsvg;
+ let force;
+ let animate = false; // should the force graph organize itself when it is displayed
+ let path, circle;
+ let savedKeys = {};
+ let width = 0;
+ let height = 0;
+
+ var getSizes = function() {
+ let legendWidth = 143;
+ let display = $('#svg_legend').css('display');
+ if (display === 'none')
+ legendWidth = 0;
+ const gap = 5;
+ let width = $('#topology').width() - gap - legendWidth;
+ let top = $('#topology').offset().top;
+ let tpformHeight = $('#topologyForm').height();
+ let height = Math.max(window.innerHeight, tpformHeight + top) - top - gap;
+ if (width < 10) {
+ QDR.log.info('page width and height are abnormal w:' + width + ' height:' + height);
+ return [0, 0];
+ }
+ return [width, height];
+ };
+ var resize = function() {
+ if (!svg)
+ return;
+ let sizes = getSizes();
+ width = sizes[0];
+ height = sizes[1];
+ if (width > 0) {
+ // set attrs and 'resume' force
+ svg.attr('width', width);
+ svg.attr('height', height);
+ force.size(sizes).resume();
+ }
+ };
+
+ $scope.$on('panel-resized', function () {
+ resize();
+ });
+ window.addEventListener('resize', resize);
+ let sizes = getSizes();
+ width = sizes[0];
+ height = sizes[1];
+ if (width <= 0 || height <= 0)
+ return;
+
+ // 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'.
+ let nodes = [];
+ let links = [];
+
+ var nodeExists = function (connectionContainer) {
+ return nodes.findIndex( function (node) {
+ return node.container === connectionContainer;
+ });
+ };
+ var normalExists = function (connectionContainer) {
+ let normalInfo = {};
+ for (let i=0; i<nodes.length; ++i) {
+ if (nodes[i].normals) {
+ if (nodes[i].normals.some(function (normal, j) {
+ if (normal.container === connectionContainer && i !== j) {
+ normalInfo = {nodesIndex: i, normalsIndex: j};
+ return true;
+ }
+ return false;
+ }))
+ break;
+ }
+ }
+ return normalInfo;
+ };
+ var getLinkSource = function (nodesIndex) {
+ for (let i=0; i<links.length; ++i) {
+ if (links[i].target === nodesIndex)
+ return i;
+ }
+ return -1;
+ };
+ var aNode = function(id, name, nodeType, nodeInfo, nodeIndex, x, y, connectionContainer, resultIndex, fixed, properties) {
+ properties = properties || {};
+ for (let i=0; i<nodes.length; ++i) {
+ if (nodes[i].name === name || nodes[i].container === connectionContainer) {
+ if (properties.product)
+ nodes[i].properties = properties;
+ return nodes[i];
+ }
+ }
+ let routerId = QDRService.management.topology.nameFromId(id);
+ return {
+ key: id,
+ name: name,
+ nodeType: nodeType,
+ properties: properties,
+ routerId: routerId,
+ x: x,
+ y: y,
+ id: nodeIndex,
+ resultIndex: resultIndex,
+ fixed: !!+fixed,
+ cls: '',
+ container: connectionContainer
+ };
+ };
+
+ var getLinkDir = function (id, connection, onode) {
+ let links = onode['router.link'];
+ if (!links) {
+ return 'unknown';
+ }
+ let inCount = 0, outCount = 0;
+ links.results.forEach( function (linkResult) {
+ let link = QDRService.utilities.flatten(links.attributeNames, linkResult);
+ if (link.linkType === 'endpoint' && link.connectionId === connection.identity)
+ if (link.linkDir === 'in')
+ ++inCount;
+ else
+ ++outCount;
+ });
+ if (inCount > 0 && outCount > 0)
+ return 'both';
+ if (inCount > 0)
+ return 'in';
+ if (outCount > 0)
+ return 'out';
+ return 'unknown';
+ };
+
+ var savePositions = function () {
+ nodes.forEach( function (d) {
+ localStorage[d.name] = angular.toJson({
+ x: Math.round(d.x),
+ y: Math.round(d.y),
+ fixed: (d.fixed & 1) ? 1 : 0,
+ });
+ });
+ };
+
+ var initializeNodes = function (nodeInfo) {
+ let nodeCount = Object.keys(nodeInfo).length;
+ let yInit = 50;
+ nodes = [];
+ for (let id in nodeInfo) {
+ let name = QDRService.management.topology.nameFromId(id);
+ // if we have any new nodes, animate the force graph to position them
+ let position = angular.fromJson(localStorage[name]);
+ if (!angular.isDefined(position)) {
+ animate = true;
+ position = {
+ x: Math.round(width / 4 + ((width / 2) / nodeCount) * nodes.length),
+ y: Math.round(height / 2 + Math.sin(nodes.length / (Math.PI*2.0)) * height / 4),
+ fixed: false,
+ };
+ //QDR.log.debug("new node pos (" + position.x + ", " + position.y + ")")
+ }
+ if (position.y > height) {
+ position.y = 200 - yInit;
+ yInit *= -1;
+ }
+ nodes.push(aNode(id, name, 'inter-router', nodeInfo, nodes.length, position.x, position.y, name, undefined, position.fixed));
+ }
+ };
+
+ var initializeLinks = function (nodeInfo, unknowns) {
+ links = [];
+ let source = 0;
+ let client = 1.0;
+ for (let id in nodeInfo) {
+ let onode = nodeInfo[id];
+ if (!onode['connection'])
+ continue;
+ let conns = onode['connection'].results;
+ let attrs = onode['connection'].attributeNames;
+ //QDR.log.debug("external client parent is " + parent);
+ let normalsParent = {}; // 1st normal node for this parent
+
+ for (let j = 0; j < conns.length; j++) {
+ let connection = QDRService.utilities.flatten(attrs, conns[j]);
+ let role = connection.role;
+ let properties = connection.properties || {};
+ let dir = connection.dir;
+ if (role == 'inter-router') {
+ let connId = connection.container;
+ let target = getContainerIndex(connId, nodeInfo);
+ if (target >= 0) {
+ getLink(source, target, dir, '', source + '-' + target);
+ }
+ } /* else if (role == "normal" || role == "on-demand" || role === "route-container")*/ {
+ // not an connection between routers, but an external connection
+ let name = QDRService.management.topology.nameFromId(id) + '.' + connection.identity;
+
+ // if we have any new clients, animate the force graph to position them
+ let position = angular.fromJson(localStorage[name]);
+ if (!angular.isDefined(position)) {
+ animate = true;
+ position = {
+ x: Math.round(nodes[source].x + 40 * Math.sin(client / (Math.PI * 2.0))),
+ y: Math.round(nodes[source].y + 40 * Math.cos(client / (Math.PI * 2.0))),
+ fixed: false
+ };
+ //QDR.log.debug("new client pos (" + position.x + ", " + position.y + ")")
+ }// else QDR.log.debug("using previous location")
+ if (position.y > height) {
+ position.y = Math.round(nodes[source].y + 40 + Math.cos(client / (Math.PI * 2.0)));
+ }
+ let existingNodeIndex = nodeExists(connection.container);
+ let normalInfo = normalExists(connection.container);
+ let node = aNode(id, name, role, nodeInfo, nodes.length, position.x, position.y, connection.container, j, position.fixed, properties);
+ let nodeType = QDRService.utilities.isAConsole(properties, connection.identity, role, node.key) ? 'console' : 'client';
+ let cdir = getLinkDir(id, connection, onode);
+ if (existingNodeIndex >= 0) {
+ // make a link between the current router (source) and the existing node
+ getLink(source, existingNodeIndex, dir, 'small', connection.name);
+ } else if (normalInfo.nodesIndex) {
+ // get node index of node that contained this connection in its normals array
+ let normalSource = getLinkSource(normalInfo.nodesIndex);
+ if (normalSource >= 0) {
+ if (cdir === 'unknown')
+ cdir = dir;
+ node.cdir = cdir;
+ nodes.push(node);
+ // create link from original node to the new node
+ getLink(links[normalSource].source, nodes.length-1, cdir, 'small', connection.name);
+ // create link from this router to the new node
+ getLink(source, nodes.length-1, cdir, 'small', connection.name);
+ // remove the old node from the normals list
+ nodes[normalInfo.nodesIndex].normals.splice(normalInfo.normalsIndex, 1);
+ }
+ } else if (role === 'normal') {
+ // normal nodes can be collapsed into a single node if they are all the same dir
+ if (cdir !== 'unknown') {
+ node.user = connection.user;
+ node.isEncrypted = connection.isEncrypted;
+ node.host = connection.host;
+ node.connectionId = connection.identity;
+ node.cdir = cdir;
+ // determine arrow direction by using the link directions
+ if (!normalsParent[nodeType+cdir]) {
+ normalsParent[nodeType+cdir] = node;
+ nodes.push(node);
+ node.normals = [node];
+ // now add a link
+ getLink(source, nodes.length - 1, cdir, 'small', connection.name);
+ client++;
+ } else {
+ normalsParent[nodeType+cdir].normals.push(node);
+ }
+ } else {
+ node.id = nodes.length - 1 + unknowns.length;
+ unknowns.push(node);
+ }
+ } else {
+ nodes.push(node);
+ // now add a link
+ getLink(source, nodes.length - 1, dir, 'small', connection.name);
+ client++;
+ }
+ }
+ }
+ source++;
+ }
+ };
+
+ // vary the following force graph attributes based on nodeCount
+ // <= 6 routers returns min, >= 80 routers returns max, interpolate linearly
+ var forceScale = function(nodeCount, min, max) {
+ let count = nodeCount;
+ if (nodeCount < 6) count = 6;
+ if (nodeCount > 80) count = 80;
+ let x = d3.scale.linear()
+ .domain([6,80])
+ .range([min, max]);
+ //QDR.log.debug("forceScale(" + nodeCount + ", " + min + ", " + max + " returns " + x(count) + " " + x(nodeCount))
+ return x(count);
+ };
+ var linkDistance = function (d, nodeCount) {
+ if (d.target.nodeType === 'inter-router')
+ return forceScale(nodeCount, 150, 70);
+ return forceScale(nodeCount, 75, 40);
+ };
+ var charge = function (d, nodeCount) {
+ if (d.nodeType === 'inter-router')
+ return forceScale(nodeCount, -1800, -900);
+ return -900;
+ };
+ var gravity = function (d, nodeCount) {
+ return forceScale(nodeCount, 0.0001, 0.1);
+ };
+ // initialize the nodes and links array from the QDRService.topology._nodeInfo object
+ var initForceGraph = function() {
+ nodes = [];
+ links = [];
+ let nodeInfo = QDRService.management.topology.nodeInfo();
+ let nodeCount = Object.keys(nodeInfo).length;
+
+ let oldSelectedNode = selected_node;
+ let oldMouseoverNode = mouseover_node;
+ mouseover_node = null;
+ selected_node = null;
+ selected_link = null;
+
+ savePositions();
+ d3.select('#SVG_ID').remove();
+ svg = d3.select('#topology')
+ .append('svg')
+ .attr('id', 'SVG_ID')
+ .attr('width', width)
+ .attr('height', height)
+ .on('click', function() {
+ removeCrosssection();
+ });
+
+ $(document).keyup(function(e) {
+ if (e.keyCode === 27) {
+ removeCrosssection();
+ }
+ });
+
+ // the legend
+ d3.select('#svg_legend svg').remove();
+ 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
+ mousedown_link = null;
+ mousedown_node = null;
+ mouseup_node = null;
+
+ // initialize the list of nodes
+ initializeNodes(nodeInfo);
+ savePositions();
+
+ // initialize the list of links
+ let unknowns = [];
+ initializeLinks(nodeInfo, unknowns);
+ $scope.schema = QDRService.management.schema();
+ // init D3 force layout
+ force = d3.layout.force()
+ .nodes(nodes)
+ .links(links)
+ .size([width, height])
+ .linkDistance(function(d) { return linkDistance(d, nodeCount); })
+ .charge(function(d) { return charge(d, nodeCount); })
+ .friction(.10)
+ .gravity(function(d) { return gravity(d, nodeCount); })
+ .on('tick', tick)
+ .on('end', function () {savePositions();})
+ .start();
+
+ svg.append('svg:defs').selectAll('marker')
+ .data(['end-arrow', 'end-arrow-selected', 'end-arrow-small', 'end-arrow-highlighted']) // 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('markerWidth', 4)
+ .attr('markerHeight', 4)
+ .attr('orient', 'auto')
+ .classed('small', function (d) {return d.indexOf('small') > -1;})
+ .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', 'start-arrow-small', 'start-arrow-highlighted']) // 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');
+
+ let grad = svg.append('svg:defs').append('linearGradient')
+ .attr('id', 'half-circle')
+ .attr('x1', '0%')
+ .attr('x2', '0%')
+ .attr('y1', '100%')
+ .attr('y2', '0%');
+ grad.append('stop').attr('offset', '50%').style('stop-color', '#C0F0C0');
+ grad.append('stop').attr('offset', '50%').style('stop-color', '#F0F000');
+
+ // handles to link and node element groups
+ path = svg.append('svg:g').selectAll('path'),
+ circle = svg.append('svg:g').selectAll('g');
+
+ // app starts here
+ restart(false);
+ force.start();
+ if (oldSelectedNode) {
+ d3.selectAll('circle.inter-router').classed('selected', function (d) {
+ if (d.key === oldSelectedNode.key) {
+ selected_node = d;
+ return true;
+ }
+ return false;
+ });
+ }
+ if (oldMouseoverNode && selected_node) {
+ d3.selectAll('circle.inter-router').each(function (d) {
+ if (d.key === oldMouseoverNode.key) {
+ mouseover_node = d;
+ QDRService.management.topology.ensureAllEntities([{entity: 'router.node', attrs: ['id','nextHop']}], function () {
+ nextHop(selected_node, d);
+ restart();
+ });
+ }
+ });
+ }
+ setTimeout(function () {
+ updateForm(Object.keys(QDRService.management.topology.nodeInfo())[0], 'router', 0);
+ });
+
+ // if any clients don't yet have link directions, get the links for those nodes and restart the graph
+ if (unknowns.length > 0)
+ setTimeout(resolveUnknowns, 10, nodeInfo, unknowns);
+
+ var continueForce = function (extra) {
+ if (extra > 0) {
+ --extra;
+ force.start();
+ setTimeout(continueForce, 100, extra);
+ }
+ };
+ continueForce(forceScale(nodeCount, 0, 200)); // give graph time to settle down
+ };
+
+ var resolveUnknowns = function (nodeInfo, unknowns) {
+ let unknownNodes = {};
+ // collapse the unknown node.keys using an object
+ for (let i=0; i<unknowns.length; ++i) {
+ unknownNodes[unknowns[i].key] = 1;
+ }
+ unknownNodes = Object.keys(unknownNodes);
+ //QDR.log.info("-- resolveUnknowns: ensuring .connection and .router.link are present for each node")
+ QDRService.management.topology.ensureEntities(unknownNodes, [{entity: 'connection', force: true}, {entity: 'router.link', attrs: ['linkType','connectionId','linkDir'], force: true}], function () {
+ nodeInfo = QDRService.management.topology.nodeInfo();
+ initializeLinks(nodeInfo, []);
+ // collapse any router-container nodes that are duplicates
+ animate = true;
+ force.nodes(nodes).links(links).start();
+ restart(false);
+ });
+ };
+
+ function updateForm(key, entity, resultIndex) {
+ if (!angular.isDefined(resultIndex))
+ return;
+ let nodeList = QDRService.management.topology.nodeIdList();
+ if (nodeList.indexOf(key) > -1) {
+ QDRService.management.topology.fetchEntities(key, [
+ {entity: entity},
+ {entity: 'listener', attrs: ['role', 'port']}], function (results) {
+ let onode = results[key];
+ if (!onode[entity]) {
+ console.log('requested ' + entity + ' but didn\'t get it');
+ return;
+ }
+ let nodeResults = onode[entity].results[resultIndex];
+ let nodeAttributes = onode[entity].attributeNames;
+ let 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
+ let 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') {
+ let listeners = onode['listener'].results;
+ let listenerAttributes = onode['listener'].attributeNames;
+ let normals = listeners.filter(function(listener) {
+ return QDRService.utilities.valFor(listenerAttributes, listener, 'role') === 'normal';
+ });
+ let ports = [];
+ normals.forEach(function(normalListener) {
+ ports.push(QDRService.utilities.valFor(listenerAttributes, normalListener, 'port'));
+ });
+ // add as 2nd row
+ if (ports.length) {
+ attributes.splice(1, 0, {
+ attributeName: 'Listening on',
+ attributeValue: ports,
+ description: 'The port(s) on which this router is listening for connections'
+ });
+ }
+ }
+ $rootScope.$broadcast('showEntityForm', {
+ entity: entity,
+ attributes: attributes
+ });
+ if (!$scope.$$phase) $scope.$apply();
+ });
+ }
+ }
+
+ function getContainerIndex(_id, nodeInfo) {
+ let nodeIndex = 0;
+ for (let id in nodeInfo) {
+ if (QDRService.management.topology.nameFromId(id) === _id)
+ return nodeIndex;
+ ++nodeIndex;
+ }
+ return -1;
+ }
+
+ function getLink(_source, _target, dir, cls, uid) {
+ for (let i = 0; i < links.length; i++) {
+ let 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);
+ if (links.some( function (l) { return l.uid === uid;}))
+ uid = uid + '.' + links.length;
+ let link = {
+ source: _source,
+ target: _target,
+ left: dir != 'out',
+ right: (dir == 'out' || dir == 'both'),
+ cls: cls,
+ uid: uid,
+ };
+ return links.push(link) - 1;
+ }
+
+
+ function resetMouseVars() {
+ mousedown_node = null;
+ mouseover_node = null;
+ mouseup_node = null;
+ mousedown_link = null;
+ }
+
+ // update force layout (called automatically each iteration)
+ function tick() {
+ circle.attr('transform', function(d) {
+ let cradius;
+ if (d.nodeType == 'inter-router') {
+ cradius = d.left ? radius + 8 : radius;
+ } else {
+ cradius = d.left ? radiusNormal + 18 : radiusNormal;
+ }
+ d.x = Math.max(d.x, radiusNormal * 2);
+ d.y = Math.max(d.y, radiusNormal * 2);
+ d.x = Math.max(0, Math.min(width - cradius, d.x));
+ d.y = Math.max(0, Math.min(height - cradius, d.y));
+ return 'translate(' + d.x + ',' + d.y + ')';
+ });
+
+ // draw directed edges with proper padding from node centers
+ path.attr('d', function(d) {
+ let sourcePadding, targetPadding, r;
+
+ if (d.target.nodeType == 'inter-router') {
+ r = radius;
+ // right arrow left line start
+ sourcePadding = d.left ? radius + 8 : radius;
+ // left arrow right line start
+ targetPadding = d.right ? radius + 16 : radius;
+ } else {
+ r = radiusNormal - 18;
+ sourcePadding = d.left ? radiusNormal + 18 : radiusNormal;
+ targetPadding = d.right ? radiusNormal + 16 : radiusNormal;
+ }
+ let dtx = Math.max(targetPadding, Math.min(width - r, d.target.x)),
+ dty = Math.max(targetPadding, Math.min(height - r, d.target.y)),
+ dsx = Math.max(sourcePadding, Math.min(width - r, d.source.x)),
+ dsy = Math.max(sourcePadding, Math.min(height - r, d.source.y));
+
+ let deltaX = dtx - dsx,
+ deltaY = dty - dsy,
+ dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ if (dist == 0)
+ dist = 0.001;
+ let normX = deltaX / dist,
+ normY = deltaY / dist;
+ let sourceX = dsx + (sourcePadding * normX),
+ sourceY = dsy + (sourcePadding * normY),
+ targetX = dtx - (targetPadding * normX),
+ targetY = dty - (targetPadding * normY);
+ sourceX = Math.max(0, sourceX);
+ sourceY = Math.max(0, sourceY);
+ targetX = Math.max(0, targetX);
+ targetY = Math.max(0, targetY);
+
+ return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
+ });
+
+ 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);
+ let sInfo = QDRService.management.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;
+ let aAr = sInfo['router.node'].attributeNames;
+ let vAr = sInfo['router.node'].results;
+ for (let hIdx = 0; hIdx < vAr.length; ++hIdx) {
+ let addrT = QDRService.utilities.valFor(aAr, vAr[hIdx], 'id');
+ if (addrT == d.name) {
+ //QDR.log.debug("found " + d.name + " at " + hIdx);
+ let nextHop = QDRService.utilities.valFor(aAr, vAr[hIdx], 'nextHop');
+ //QDR.log.debug("nextHop was " + nextHop);
+ return (nextHop == null) ? nodeFor(addrT) : nodeFor(nextHop);
+ }
+ }
+ return null;
+ }
+
+ function nodeFor(name) {
+ for (let i = 0; i < nodes.length; ++i) {
+ if (nodes[i].name == name)
+ return nodes[i];
+ }
+ return null;
+ }
+
+ function linkFor(source, target) {
+ for (let 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() {
+ d3.select('#crosssection svg g').transition()
+ .duration(1000)
+ .attr('transform', 'scale(0)')
+ .style('opacity', 0)
+ .each('end', function () {
+ d3.select('#crosssection svg').remove();
+ d3.select('#crosssection').style('display','none');
+ });
+ d3.select('#multiple_details').transition()
+ .duration(500)
+ .style('opacity', 0)
+ .each('end', function() {
+ d3.select('#multiple_details').style('display', 'none');
+ stopUpdateConnectionsGrid();
+ });
+ hideLinkDetails();
+ }
+
+ function hideLinkDetails() {
+ d3.select('#link_details').transition()
+ .duration(500)
+ .style('opacity', 0)
+ .each('end', function() {
+ d3.select('#link_details').style('display', 'none');
+ });
+ }
+
+ function clerAllHighlights() {
+ for (let i = 0; i < links.length; ++i) {
+ links[i]['highlighted'] = false;
+ }
+ for (let i = 0; i<nodes.length; ++i) {
+ nodes[i]['highlighted'] = false;
+ }
+ }
+ // 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, function(d) {return d.uid;});
+
+ // update existing links
+ path.classed('selected', function(d) {
+ return d === selected_link;
+ })
+ .classed('highlighted', function(d) {
+ return d.highlighted;
+ })
+ .attr('marker-start', function(d) {
+ let sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+ if (d.highlighted)
+ sel = '-highlighted';
+ return d.left ? 'url(' + urlPrefix + '#start-arrow' + sel + ')' : '';
+ })
+ .attr('marker-end', function(d) {
+ let sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+ if (d.highlighted)
+ sel = '-highlighted';
+ return d.right ? 'url(' + urlPrefix + '#end-arrow' + sel + ')' : '';
+ });
+ // add new links. if a link with a new uid is found in the data, add a new path
+ path.enter().append('svg:path')
+ .attr('class', 'link')
+ .attr('marker-start', function(d) {
+ let sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+ return d.left ? 'url(' + urlPrefix + '#start-arrow' + sel + ')' : '';
+ })
+ .attr('marker-end', function(d) {
+ let sel = d === selected_link ? '-selected' : (d.cls === 'small' ? '-small' : '');
+ return d.right ? 'url(' + urlPrefix + '#end-arrow' + sel + ')' : '';
+ })
+ .classed('small', function(d) {
+ return d.cls == 'small';
+ })
+ .on('mouseover', function(d) { // mouse over a path
+ let resultIndex = 0; // the connection to use
+ let left = d.left ? d.target : d.source;
+ // right is the node that the arrow points to, left is the other node
+ let right = d.left ? d.source : d.target;
+ let onode = QDRService.management.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) {
+ let conn = onode['connection'].results[resultIndex];
+ /// find the connection whose container is the right's name
+ let name = QDRService.utilities.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;
+ }
+ updateForm(left.key, 'connection', resultIndex);
+ }
+
+ mousedown_link = d;
+ selected_link = mousedown_link;
+ restart();
+ })
+ .on('mousemove', function (d) {
+ let top = $('#topology').offset().top - 5;
+ $timeout(function () {
+ $scope.trustedpopoverContent = $sce.trustAsHtml(connectionPopupHTML(d));
+ });
+ d3.select('#popover-div')
+ .style('display', 'block')
+ .style('left', (d3.event.pageX+5)+'px')
+ .style('top', (d3.event.pageY-top)+'px');
+ })
+ .on('mouseout', function() { // mouse out of a path
+ d3.select('#popover-div')
+ .style('display', 'none');
+ selected_link = null;
+ restart();
+ })
+ // left click a path
+ .on('click', function (d) {
+ let clickPos = d3.mouse(this);
+ d3.event.stopPropagation();
+ clearPopups();
+ var showCrossSection = function() {
+ const diameter = 400;
+ let pack = d3.layout.pack()
+ .size([diameter - 4, diameter - 4])
+ .padding(-10)
+ .value(function(d) { return d.size; });
+
+ d3.select('#crosssection svg').remove();
+ let svg = d3.select('#crosssection').append('svg')
+ .attr('width', diameter)
+ .attr('height', diameter);
+
+ let rg = svg.append('svg:defs')
+ .append('radialGradient')
+ .attr('id', 'cross-gradient')
+ .attr('gradientTransform', 'scale(2.0) translate(-0.5,-0.5)');
+
+ rg
+ .append('stop')
+ .attr('offset', '0%')
+ .attr('stop-color', '#feffff');
+ rg
+ .append('stop')
+ .attr('offset', '40%')
+ .attr('stop-color', '#cfe2f3');
+
+ let svgg = svg.append('g')
+ .attr('transform', 'translate(2,2)');
+
+ svgg
+ .append('rect')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('width', 200)
+ .attr('height', 200)
+ .attr('class', 'cross-rect')
+ .attr('fill', 'url('+urlPrefix+'#cross-gradient)');
+
+ svgg
+ .append('line')
+ .attr('class', 'cross-line')
+ .attr({x1: 2, y1: 0, x2: 200, y2: 0});
+ svgg
+ .append('line')
+ .attr('class', 'cross-line')
+ .attr({x1: 2, y1: 0, x2: 0, y2: 200});
+
+ /*
+ let simpleLine = d3.svg.line();
+ svgg
+ .append('path')
+ .attr({
+ d: simpleLine([[0,0],[0,200]]),
+ stroke: '#000',
+ 'stroke-width': '4px'
+ });
+ svgg
+ .append('path')
+ .attr({
+ d: simpleLine([[0,0],[200,0]]),
+ stroke: '#000',
+ 'stroke-width': '4px'
+ });
+*/
+ let root = {
+ name: ' Links between ' + d.source.name + ' and ' + d.target.name,
+ children: []
+ };
+ let nodeInfo = QDRService.management.topology.nodeInfo();
+ let connections = nodeInfo[d.source.key]['connection'];
+ let 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
+ let links = nodeInfo[d.source.key]['router.link'];
+ let identityIndex = connections.attributeNames.indexOf('identity');
+ let roleIndex = connections.attributeNames.indexOf('role');
+ let connectionIdIndex = links.attributeNames.indexOf('connectionId');
+ let linkTypeIndex = links.attributeNames.indexOf('linkType');
+ let nameIndex = links.attributeNames.indexOf('name');
+ let 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] + ' ',
+ size: 100,
+ obj: link,
+ desc: 'Link',
+ attributeNames: links.attributeNames
+ });
+ });
+ if (root.children.length == 0)
+ return;
+ let 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)' : ''); });
+ node.append('circle')
+ .attr('r', function(d) { return d.r; });
+
+ node.on('mouseenter', function (d) {
+ let 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>';
+ $timeout( (function () {
+ $scope.crosshtml = $sce.trustAsHtml(title);
+ $('#crosshtml').show();
+ let parent = $('#crosssection');
+ let ppos = parent.position();
+ let mleft = ppos.left + parent.width();
+ $('#crosshtml').css({left: mleft, top: ppos.top});
+ }).bind(this));
+ });
+ node.on('mouseout', function () {
+ $('#crosshtml').hide();
+ });
+
+ node.append('text')
+ .attr('dy', function (d) { return d.children ? '-10em' : '.5em';})
+ .style('text-anchor', 'middle')
+ .text(function(d) {
+ return d.name.substring(0, d.r / 3);
+ });
+ svgg.attr('transform', 'translate(2,2) scale(0.01)');
+
+ let bounds = $('#topology').position();
+ d3.select('#crosssection')
+ .style('display', 'block')
+ .style('left', (clickPos[0] + bounds.left) + 'px')
+ .style('top', (clickPos[1] + bounds.top) + 'px');
+
+ svgg.transition()
+ .attr('transform', 'translate(2,2) scale(1)')
+ .each('end', function () {
+ d3.selectAll('#crosssection g.leaf text').attr('dy', '.3em');
+ });
+ };
+ QDRService.management.topology.ensureEntities(d.source.key, {entity: 'router.link', force: true}, showCrossSection);
+ });
+ // remove old links
+ path.exit().remove();
+
+
+ // circle (node) group
+ // nodes are known by id
+ circle = circle.data(nodes, function(d) {
+ return d.name;
+ });
+
+ // update existing nodes visual states
+ circle.selectAll('circle')
+ .classed('highlighted', function(d) {
+ return d.highlighted;
+ })
+ .classed('selected', function(d) {
+ return (d === selected_node);
+ })
+ .classed('fixed', function(d) {
+ return d.fixed & 1;
+ });
+
+ // add new circle nodes. if nodes[] is longer than the existing paths, add a new path for each new element
+ let 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];
+ })
+ .attr('fill', function (d) {
+ if (d.cdir === 'both' && !QDRService.utilities.isConsole(d)) {
+ return 'url(' + urlPrefix + '#half-circle)';
+ }
+ return null;
+ })
+ .classed('fixed', function(d) {
+ return d.fixed & 1;
+ })
+ .classed('normal', function(d) {
+ return d.nodeType == 'normal' || QDRService.utilities.isConsole(d);
+ })
+ .classed('in', function(d) {
+ return d.cdir == 'in';
+ })
+ .classed('out', function(d) {
+ return d.cdir == 'out';
+ })
+ .classed('inout', function(d) {
+ return d.cdir == 'both';
+ })
+ .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.utilities.isConsole(d);
+ })
+ .classed('artemis', function(d) {
+ return QDRService.utilities.isArtemis(d);
+ })
+ .classed('qpid-cpp', function(d) {
+ return QDRService.utilities.isQpid(d);
+ })
+ .classed('route-container', function (d) {
+ return (!QDRService.utilities.isArtemis(d) && !QDRService.utilities.isQpid(d) && d.nodeType === 'route-container');
+ })
+ .classed('client', function(d) {
+ return d.nodeType === 'normal' && !d.properties.console_identifier;
+ });
+ };
+ appendCircle(g)
+ .on('mouseover', function(d) { // mouseover a circle
+ if (!selected_node && !mousedown_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' || d.nodeType === 'route-container') {
+ //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;
+ }
+ clerAllHighlights();
+ // we need .router.node info to highlight hops
+ QDRService.management.topology.ensureAllEntities([{entity: 'router.node', attrs: ['id','nextHop']}], function () {
+ mouseover_node = d; // save this node in case the topology changes so we can restore the highlights
+ nextHop(selected_node, d);
+ restart();
+ });
+ })
+ .on('mouseout', function() { // mouse out for a circle
+ // unenlarge target node
+ d3.select(this).attr('transform', '');
+ clerAllHighlights();
+ mouseover_node = null;
+ restart();
+ })
+ .on('mousedown', function(d) { // mouse down for circle
+ 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.parentNode.parentNode.parentNode).slice();
+ })
+ .on('mouseup', function(d) { // mouse up for circle
+ if (!mousedown_node)
+ return;
+
+ selected_link = null;
+ // unenlarge target node
+ d3.select(this).attr('transform', '');
+
+ // check for drag
+ mouseup_node = d;
+
+ let mySvg = this.parentNode.parentNode.parentNode;
+ // if we dragged the node, make it fixed
+ let cur_mouse = d3.mouse(mySvg);
+ if (cur_mouse[0] != initial_mouse_down_position[0] ||
+ cur_mouse[1] != initial_mouse_down_position[1]) {
+ d.fixed = true;
+ setNodesFixed(d.name, true);
+ resetMouseVars();
+ 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;
+ }
+ clerAllHighlights();
+ mousedown_node = null;
+ if (!$scope.$$phase) $scope.$apply();
+ restart(false);
+
+ })
+ .on('dblclick', function(d) { // circle
+ if (d.fixed) {
+ d.fixed = false;
+ setNodesFixed(d.name, false);
+ restart(); // redraw the node without a dashed line
+ force.start(); // let the nodes move to a new position
+ }
+ })
+ .on('contextmenu', function(d) { // circle
+ $(document).click();
+ d3.event.preventDefault();
+ let rm = relativeMouse();
+ d3.select('#node_context_menu')
+ .style({
+ display: 'block',
+ left: rm.left + 'px',
+ top: (rm.top - rm.offset.top) + 'px'
+ });
+ $timeout( function () {
+ $scope.contextNode = d;
+ });
+ })
+ .on('click', function(d) { // circle
+ if (!mouseup_node)
+ return;
+ // clicked on a circle
+ clearPopups();
+ if (!d.normals) {
+ // circle was a router or a broker
+ if (QDRService.utilities.isArtemis(d)) {
+ const artemisPath = '/jmx/attributes?tab=artemis&con=Artemis';
+ if (QDR.isStandalone)
+ window.location = $location.protocol() + '://localhost:8161/hawtio' + artemisPath;
+ else
+ $location.path(artemisPath);
+ }
+ return;
+ }
+ d3.event.stopPropagation();
+ startUpdateConnectionsGrid(d);
+ });
+ //.attr("transform", function (d) {return "scale(" + (d.nodeType === 'normal' ? .5 : 1) + ")"})
+ //.transition().duration(function (d) {return d.nodeType === 'normal' ? 3000 : 0}).ease("elastic").attr("transform", "scale(1)")
+
+ var appendContent = function(g) {
+ // show node IDs
+ g.append('svg:text')
+ .attr('x', 0)
+ .attr('y', function(d) {
+ let y = 7;
+ if (QDRService.utilities.isArtemis(d))
+ y = 8;
+ else if (QDRService.utilities.isQpid(d))
+ y = 9;
+ else if (d.nodeType === 'inter-router')
+ y = 4;
+ else if (d.nodeType === 'route-container')
+ y = 5;
+ return y;
+ })
+ .attr('class', 'id')
+ .classed('console', function(d) {
+ return QDRService.utilities.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.utilities.isArtemis(d);
+ })
+ .classed('qpid-cpp', function(d) {
+ return QDRService.utilities.isQpid(d);
+ })
+ .text(function(d) {
+ if (QDRService.utilities.isConsole(d)) {
+ return '\uf108'; // icon-desktop for this console
+ } else if (QDRService.utilities.isArtemis(d)) {
+ return '\ue900';
+ } else if (QDRService.utilities.isQpid(d)) {
+ return '\ue901';
+ } else if (d.nodeType === 'route-container') {
+ return d.properties.product ? d.properties.product[0].toUpperCase() : 'S';
+ } else 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) {
+ let x = '';
+ if (d.normals && d.normals.length > 1)
+ x = ' x ' + d.normals.length;
+ if (QDRService.utilities.isConsole(d)) {
+ return 'Dispatch console' + x;
+ } else if (QDRService.utilities.isArtemis(d)) {
+ return 'Broker - Artemis' + x;
+ } else if (d.properties.product == 'qpid-cpp') {
+ return 'Broker - qpid-cpp' + x;
+ } else if (d.properties.product) {
+ return d.properties.product;
+ } else if (d.cdir === 'in')
+ return 'Sender' + x;
+ else if (d.cdir === 'out')
+ return 'Receiver' + x;
+ else if (d.cdir === 'both')
+ return 'Sender/Receiver' + 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();
+ let 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
+ // the legend
+ d3.select('#svg_legend svg').remove();
+ 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');
+ let legendNodes = [];
+ legendNodes.push(aNode('Router', '', 'inter-router', '', undefined, 0, 0, 0, 0, false, {}));
+
+ if (!svg.selectAll('circle.console').empty()) {
+ legendNodes.push(aNode('Console', '', 'normal', '', undefined, 1, 0, 0, 0, false, {
+ console_identifier: 'Dispatch console'
+ }));
+ }
+ if (!svg.selectAll('circle.client.in').empty()) {
+ let node = aNode('Sender', '', 'normal', '', undefined, 2, 0, 0, 0, false, {});
+ node.cdir = 'in';
+ legendNodes.push(node);
+ }
+ if (!svg.selectAll('circle.client.out').empty()) {
+ let node = aNode('Receiver', '', 'normal', '', undefined, 3, 0, 0, 0, false, {});
+ node.cdir = 'out';
+ legendNodes.push(node);
+ }
+ if (!svg.selectAll('circle.client.inout').empty()) {
+ let node = aNode('Sender/Receiver', '', 'normal', '', undefined, 4, 0, 0, 0, false, {});
+ node.cdir = 'both';
+ legendNodes.push(node);
+ }
+ if (!svg.selectAll('circle.qpid-cpp').empty()) {
+ legendNodes.push(aNode('Qpid broker', '', 'route-container', '', undefined, 5, 0, 0, 0, false, {
+ product: 'qpid-cpp'
+ }));
+ }
+ if (!svg.selectAll('circle.artemis').empty()) {
+ legendNodes.push(aNode('Artemis broker', '', 'route-container', '', undefined, 6, 0, 0, 0, false,
+ {product: 'apache-activemq-artemis'}));
+ }
+ if (!svg.selectAll('circle.route-container').empty()) {
+ legendNodes.push(aNode('Service', '', 'route-container', 'external-service', undefined, 7, 0, 0, 0, false,
+ {product: ' External Service'}));
+ }
+ lsvg = lsvg.data(legendNodes, function(d) {
+ return d.key;
+ });
+ let 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();
+ let svgEl = document.getElementById('svglegend');
+ if (svgEl) {
+ let bb;
+ // firefox can throw an exception on getBBox on an svg element
+ try {
+ bb = svgEl.getBBox();
+ } catch (e) {
+ bb = {
+ y: 0,
+ height: 200,
+ x: 0,
+ width: 200
+ };
+ }
+ 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) {
+ // called after each topology update
+ var extendConnections = function() {
+ // force a fetch of the links for this node
+ QDRService.management.topology.ensureEntities(d.key, {entity: 'router.link', force: true}, function () {
+ // the links for this node are now available
+ $scope.multiData = [];
+ let 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) {
+ let nodeInfo = QDRService.management.topology.nodeInfo();
+ let links = nodeInfo[n.key]['router.link'];
+ let linkTypeIndex = links.attributeNames.indexOf('linkType');
+ let connectionIdIndex = links.attributeNames.indexOf('connectionId');
+ n.linkData = [];
+ links.results.forEach(function(link) {
+ if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.connectionId) {
+ let l = {};
+ let ll = QDRService.utilities.flatten(links.attributeNames, link);
+ l.owningAddr = ll.owningAddr;
+ l.dir = ll.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 = ll.deliveryCount;
+ l.uncounts = QDRService.utilities.pretty(ll.undeliveredCount + ll.unsettledCount);
+ l.adminStatus = ll.adminStatus;
+ l.operStatus = ll.operStatus;
+ l.identity = ll.identity;
+ l.connectionId = ll.connectionId;
+ l.nodeId = n.key;
+ l.type = ll.type;
+ l.name = ll.name;
+
+ // 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
+ let 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
+ let 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) + 40 + 'px',
+ 'overflow-y': normals.length > 10 ? 'scroll' : 'hidden'
+ });
+ });
+ };
+ $scope.connectionsStyle = function () {
+ return {
+ height: ($scope.multiData.length * 30 + 40) + 'px'
+ };
+ };
+ $scope.linksStyle = function () {
+ return {
+ height: ($scope.linkData.length * 30 + 40) + 'px'
+ };
+ };
+ // register a notification function for when the topology is updated
+ QDRService.management.topology.addUpdatedAction('normalsStats', extendConnections);
+ // call the function that gets the links right now
+ extendConnections();
+ clearPopups();
+ let display = 'block';
+ if (d.normals.length === 1) {
+ display = 'none';
+ mouseY = mouseY - 20;
+ }
+ let rm = relativeMouse();
+ d3.select('#multiple_details')
+ .style({
+ display: display,
+ opacity: 1,
+ left: rm.left + 'px',
+ top: (rm.top - rm.offset.top) + 'px'
+ });
+ if (d.normals.length === 1) {
+ // simulate a click on the connection to popup the link details
+ QDRService.management.topology.ensureEntities(d.key, {entity: 'router.link', force: true}, function () {
+ $scope.multiDetails.showLinksList({
+ entity: d
+ });
+ });
+ }
+ };
+ var stopUpdateConnectionsGrid = function() {
+ QDRService.management.topology.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)) {
+ let target = findNextHopNode(thisNode, d);
+ //QDR.log.debug("highlight link from node ");
+ //console.dump(nodeFor(selected_node.name));
+ //console.dump(target);
+ if (target) {
+ let hnode = nodeFor(thisNode.name);
+ let hlLink = linkFor(hnode, target);
+ //QDR.log.debug("need to highlight");
+ //console.dump(hlLink);
+ if (hlLink) {
+ hlLink['highlighted'] = true;
+ hnode['highlighted'] = true;
+ }
+ else
+ target = null;
+ }
+ nextHop(target, d);
+ }
+ if (thisNode == d) {
+ let hnode = nodeFor(thisNode.name);
+ hnode['highlighted'] = true;
+ }
+ }
+
+ 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
+ let nodeInfo = QDRService.management.topology.nodeInfo();
+ // don't count the nodes without connection info
+ let cnodes = Object.keys(nodeInfo).filter ( function (node) {
+ return (nodeInfo[node]['connection']);
+ });
+ let routers = nodes.filter( function (node) {
+ return node.nodeType === 'inter-router';
+ });
+ if (routers.length > cnodes.length) {
+ return -1;
+ }
+
+
+ if (cnodes.length != Object.keys(savedKeys).length) {
+ return cnodes.length > Object.keys(savedKeys).length ? 1 : -1;
+ }
+ // we may have dropped a node and added a different node in the same update cycle
+ for (let i=0; i<cnodes.length; i++) {
+ let key = cnodes[i];
+ // if this node isn't in the saved node list
+ if (!savedKeys.hasOwnProperty(key))
+ return 1;
+ // if the number of connections for this node chaanged
+ if (!nodeInfo[key]['connection'])
+ return -1;
+ if (nodeInfo[key]['connection'].results.length != savedKeys[key]) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ function saveChanged() {
+ savedKeys = {};
+ let nodeInfo = QDRService.management.topology.nodeInfo();
+ // save the number of connections per node
+ for (let key in nodeInfo) {
+ if (nodeInfo[key]['connection'])
+ savedKeys[key] = nodeInfo[key]['connection'].results.length;
+ }
+ }
+ // we are about to leave the page, save the node positions
+ $rootScope.$on('$locationChangeStart', function() {
+ //QDR.log.debug("locationChangeStart");
+ savePositions();
+ });
+ // When the DOM element is removed from the page,
+ // AngularJS will trigger the $destroy event on
+ // the scope
+ $scope.$on('$destroy', function() {
+ //QDR.log.debug("scope on destroy");
+ savePositions();
+ QDRService.management.topology.setUpdateEntities([]);
+ QDRService.management.topology.stopUpdating();
+ QDRService.management.topology.delUpdatedAction('normalsStats');
+ QDRService.management.topology.delUpdatedAction('topology');
+
+ d3.select('#SVG_ID').remove();
+ window.removeEventListener('resize', resize);
+ });
+
+ function handleInitialUpdate() {
+ // we only need to update connections during steady-state
+ QDRService.management.topology.setUpdateEntities(['connection']);
+ // we currently have all entities available on all routers
+ saveChanged();
+ initForceGraph();
+ // after the graph is displayed fetch all .router.node info. This is done so highlighting between nodes
+ // doesn't incur a delay
+ QDRService.management.topology.addUpdateEntities({entity: 'router.node', attrs: ['id','nextHop']});
+ // call this function every time a background update is done
+ QDRService.management.topology.addUpdatedAction('topology', function() {
+ let changed = hasChanged();
+ // there is a new node, we need to get all of it's entities before drawing the graph
+ if (changed > 0) {
+ QDRService.management.topology.delUpdatedAction('topology');
+ animate = true;
+ setupInitialUpdate();
+ } else if (changed === -1) {
+ // we lost a node (or a client), we can draw the new svg immediately
+ animate = false;
+ saveChanged();
+ let nodeInfo = QDRService.management.topology.nodeInfo();
+ initializeNodes(nodeInfo);
+
+ let unknowns = [];
+ initializeLinks(nodeInfo, unknowns);
+ if (unknowns.length > 0) {
+ resolveUnknowns(nodeInfo, unknowns);
+ }
+ else {
+ force.nodes(nodes).links(links).start();
+ restart();
+ }
+
+ //initForceGraph();
+ } else {
+ //QDR.log.debug("topology didn't change")
+ }
+
+ });
+ }
+
+ function setupInitialUpdate() {
+ // make sure all router nodes have .connection info. if not then fetch any missing info
+ QDRService.management.topology.ensureAllEntities(
+ // [{entity: ".connection"}, {entity: ".router.lin.router.link", attrs: ["linkType","connectionId","linkDir"]}],
+ [{entity: 'connection'}],
+ //[{entity: ".connection"}],
+ handleInitialUpdate);
+ }
+ if (!QDRService.management.connection.is_connected()) {
+ // we are not connected. we probably got here from a bookmark or manual page reload
+ QDR.redirectWhenConnected($location, 'topology');
+ return;
+ }
+
+ let connectionPopupHTML = function (d) {
+ let left = d.left ? d.source : d.target;
+ // left is the connection with dir 'in'
+ let right = d.left ? d.target : d.source;
+ let onode = QDRService.management.topology.nodeInfo()[left.key];
+ let connSecurity = function (conn) {
+ if (!conn.isEncrypted)
+ return 'no-security';
+ if (conn.sasl === 'GSSAPI')
+ return 'Kerberos';
+ return conn.sslProto + '(' + conn.sslCipher + ')';
+ };
+ let connAuth = function (conn) {
+ if (!conn.isAuthenticated)
+ return 'no-auth';
+ let sasl = conn.sasl;
+ if (sasl === 'GSSAPI')
+ sasl = 'Kerberos';
+ else if (sasl === 'EXTERNAL')
+ sasl = 'x.509';
+ else if (sasl === 'ANONYMOUS')
+ return 'anonymous-user';
+ if (!conn.user)
+ return sasl;
+ return conn.user + '(' + sasl + ')';
+ };
+ let connTenant = function (conn) {
+ if (!conn.tenant) {
+ return '';
+ }
+ if (conn.tenant.length > 1)
+ return conn.tenant.replace(/\/$/, '');
+ };
+ // loop through all the connections for left, and find the one for right
+ let rightIndex = onode['connection'].results.findIndex( function (conn) {
+ return QDRService.utilities.valFor(onode['connection'].attributeNames, conn, 'container') === right.routerId;
+ });
+ if (rightIndex < 0) {
+ // we have a connection to a client/service
+ rightIndex = +left.connectionId;
+ }
+ if (isNaN(rightIndex)) {
+ // we have a connection to a console
+ rightIndex = +right.connectionId;
+ }
+ let HTML = '';
+ if (rightIndex >= 0) {
+ let conn = onode['connection'].results[rightIndex];
+ conn = QDRService.utilities.flatten(onode['connection'].attributeNames, conn);
+ HTML += '<table class="popupTable">';
+ HTML += ('<tr><td>Security</td><td>' + connSecurity(conn) + '</td></tr>');
+ HTML += ('<tr><td>Authentication</td><td>' + connAuth(conn) + '</td></tr>');
+ HTML += ('<tr><td>Tenant</td><td>' + connTenant(conn) + '</td></tr>');
+ HTML += '</table>';
+ }
+ return HTML;
+ };
+
+ animate = true;
+ setupInitialUpdate();
+ QDRService.management.topology.startUpdating(false);
+
+ }
+ ]);
+
+ return QDR;
+
+}(QDR || {}));
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[5/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js
files that were in the npm repository
Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplChartConfig.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplChartConfig.html b/console/stand-alone/plugin/html/tmplChartConfig.html
new file mode 100644
index 0000000..4c9c727
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplChartConfig.html
@@ -0,0 +1,85 @@
+<!--
+ 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
+-->
+
+<!--
+ This is the template for the graph dialog that is displayed. It uses the
+ dialogCtrl controller in qdrCharts.js.
+-->
+<div class="chartOptions">
+ <div class="modal-header">
+ <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
+ </div>
+ <div class="modal-body">
+ <div id="{{svgDivId}}" class="line-chart-pf"></div>
+
+ <uib-tabset>
+ <uib-tab heading="Type">
+ <legend>Chart type</legend>
+ <div>
+ <label><input type="radio" ng-model="dialogChart.type" value="value" /> Value Chart</label>
+ <label><input type="radio" ng-model="dialogChart.type" value="rate" /> Rate Chart</label>
+<!--
+ <div class="dlg-slider" ng-show="dialogChart.type=='rate'">
+ <span>Rate Window: {{rateWindow}} second{{rateWindow > 1 ? "s" : ""}}</span>
+ <div id="rateSlider"></div>
+ </div>
+-->
+ </div>
+ <div style="clear:both;"> </div>
+ </uib-tab>
+<!--
+ <uib-tab ng-hide="$parent.chart.aggregate()" heading="Colors">
+ <legend>Chart colors</legend>
+ <div>
+ <div class="colorPicker">
+ <label>Area: <input id="areaColor" name="areaColor" type="color" /></label>
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </uib-tab>
+-->
+ <uib-tab heading="Duration">
+ <legend>Chart duration</legend>
+ <div class="clearfix">
+ <div class="dlg-slider duration">
+ <span>Show data for past {{dialogChart.visibleDuration}} minute{{dialogChart.visibleDuration > 1 ? "s" : ""}}</span>
+ <div id="durationSlider"></div>
+ </div>
+ </div>
+ </uib-tab>
+ </uib-tabset>
+ </div>
+ <div class="modal-footer">
+ <div ng-hide="adding()">
+ <button class="btn btn-success" type="button" ng-click="apply()">Apply to existing chart</button>
+ <button class="btn btn-info" type="button" ng-click="copyToDashboard()">Create new chart</button>
+ <button class="btn btn-primary" type="button" ng-click="okClick()">Close</button>
+ </div>
+ <div ng-show="adding()">
+ <span ng-hide="isOnChartsPage()">
+ <button class="btn btn-success" type="button" ng-click="addChartsPage()"><i class="icon-bar-chart"></i> Add</button> this chart to the Charts page.
+ </span>
+ <span ng-show="isOnChartsPage()">
+ View the <button class="btn btn-success" type="button" ng-click="showChartsPage()"><i class="icon-bar-chart"></i> Charts</button> page.
+ </span>
+ <button class="btn btn-primary" type="button" ng-click="okClick()">Close</button>
+ </div>
+ </div>
+</div>
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplListChart.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplListChart.html b/console/stand-alone/plugin/html/tmplListChart.html
new file mode 100644
index 0000000..3214187
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplListChart.html
@@ -0,0 +1,40 @@
+<!--
+ 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
+-->
+
+<!--
+ This is the template for the graph dialog that is displayed.
+-->
+<div class="modal-header">
+ <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
+</div>
+<div class="modal-body">
+ <p class="newChart">
+ <button ng-click="editChart()" title="Configure"><span class="fa-edit"></span></button>
+ </p><div style="clear:both"></div>
+ <div id="pfDialogChart" class="line-chart-pf"></div>
+</div>
+<div class="modal-footer">
+ <span ng-hide="isOnChartsPage()">
+ <button class="btn btn-success" type="button" ng-click="addChartsPage()"><i class="icon-bar-chart"></i> Add</button> this chart to the Charts page.
+ </span>
+ <span ng-show="isOnChartsPage()">
+ <button class="btn btn-danger" type="button" ng-click="delChartsPage()">Remove</button> this chart from the <button class="btn btn-success" type="button" ng-click="showChartsPage()"><i class="icon-bar-chart"></i> Charts</button> page.
+ </span>
+ <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
+</div>
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplListTree.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplListTree.html b/console/stand-alone/plugin/html/tmplListTree.html
new file mode 100644
index 0000000..64797fa
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplListTree.html
@@ -0,0 +1,42 @@
+<!--
+ 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
+-->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <ng-include src="'listGrid.html'"></ng-include>
+</div>
+<div class="qdr-attributes col-sm-3 col-md-2 col-sm-pull-9 col-md-pull-10 sidebar-pf sidebar-pf-left">
+ <div class="pane-wrapper">
+ <div class="pane-header-wrapper">
+ <div class="tree-header"><select ng-options="node as node.name for node in nodes" ng-model="currentNode" ng-change="selectNode(currentNode)"></select></div>
+ <div ng-hide="largeNetwork" class="expand-collapse">
+ <i class="icon-chevron-down clickable" title="Expand all nodes" ng-click="expandAll()"></i>
+ <i class="icon-chevron-up clickable" title="Unexpand all nodes" ng-click="contractAll()"></i>
+ </div>
+ </div>
+ <div class="pane-viewport">
+ <div class="pane-content">
+ <div class="treeContainer">
+ <div id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div>
+ <div ng-init="treeReady()"></div>
+ </div>
+ </div>
+ </div>
+ <div class="pane-bar"></div>
+ </div>
+</div>
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplOverviewTree.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplOverviewTree.html b/console/stand-alone/plugin/html/tmplOverviewTree.html
new file mode 100644
index 0000000..d3f6e80
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplOverviewTree.html
@@ -0,0 +1,48 @@
+<!--
+ 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
+-->
+<div class="col-xs-9">
+ <ng-include src="'overviewGrid.html'"></ng-include>
+</div>
+
+<button type='button' class='navbar-toggle tree-menu-button' data-toggle="collapse" data-target=".overview-tree">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+
+<div class="overview-tree navbar-collapse collapse col-xs-3">
+ <div class="pane-wrapper">
+ <div class="pane-header-wrapper">
+ <div ng-hide="largeNetwork" class="expand-collapse">
+ <i class="icon-chevron-down clickable" title="Expand all nodes" ng-click="expandAll()"></i>
+ <i class="icon-chevron-up clickable" title="Unexpand all nodes" ng-click="contractAll()"></i>
+ </div>
+ </div>
+ <div class="pane-viewport">
+ <div class="pane-content">
+ <div class="treeContainer ng-scope">
+ <div id="overtree"></div>
+ </div>
+ </div>
+ </div>
+ <div class="pane-bar" ng-mousedown="startMoving($event)" ng-click="toggle()"></div>
+ </div>
+</div>
+<div ng-init="overviewLoaded()"></div>
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/dlgChartController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/dlgChartController.js b/console/stand-alone/plugin/js/dlgChartController.js
new file mode 100644
index 0000000..e8c240f
--- /dev/null
+++ b/console/stand-alone/plugin/js/dlgChartController.js
@@ -0,0 +1,204 @@
+/*
+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.
+*/
+'use strict';
+/* global angular */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ // controller for the edit/configure chart dialog
+ QDR.module.controller('QDR.ChartDialogController', function($scope, QDRChartService, $location, $uibModalInstance, chart, updateTick, dashboard, adding) {
+ let dialogSvgChart = null;
+ $scope.svgDivId = 'dialogEditChart'; // the div id for the svg chart
+
+ let updateTimer = null;
+ $scope.chart = chart; // the underlying chart object from the dashboard
+ $scope.dialogChart = $scope.chart.copy(); // the chart object for this dialog
+ $scope.userTitle = $scope.chart.title();
+
+ $scope.$watch('userTitle', function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ $scope.dialogChart.title(newValue);
+ dialogSvgChart.tick($scope.svgDivId);
+ }
+ });
+ $scope.$watch('dialogChart.areaColor', function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ if (dialogSvgChart)
+ dialogSvgChart.tick($scope.svgDivId);
+ }
+ });
+ $scope.$watch('dialogChart.lineColor', function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ if (dialogSvgChart)
+ dialogSvgChart.tick($scope.svgDivId);
+ }
+ });
+ $scope.$watch('dialogChart.type', function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ if (dialogSvgChart) {
+ dialogSvgChart.chart.visibleDuration = newValue === 'rate' ? 0.25 : 1;
+ dialogSvgChart.tick($scope.svgDivId);
+ }
+ }
+ });
+
+ // the stored rateWindow is in milliseconds, but the slider is in seconds
+ $scope.rateWindow = $scope.chart.rateWindow / 1000;
+
+ $scope.addChartsPage = function () {
+ QDRChartService.addDashboard(dialogSvgChart.chart);
+ };
+ $scope.delChartsPage = function () {
+ QDRChartService.delDashboard($scope.chart);
+ };
+
+ $scope.showChartsPage = function () {
+ cleanup();
+ $uibModalInstance.close(true);
+ $location.path(QDR.pluginRoot + '/charts');
+ };
+
+ var cleanup = function () {
+ if (updateTimer) {
+ clearTimeout(updateTimer);
+ updateTimer = null;
+ }
+ if (!$scope.isOnChartsPage())
+ QDRChartService.unRegisterChart($scope.dialogChart); // remove the chart
+ };
+ $scope.okClick = function () {
+ cleanup();
+ $uibModalInstance.close(true);
+ };
+
+ var initRateSlider = function () {
+ if (document.getElementById('rateSlider')) {
+ $( '#rateSlider' ).slider({
+ value: $scope.rateWindow,
+ min: 1,
+ max: 10,
+ step: 1,
+ slide: function( event, ui ) {
+ $scope.rateWindow = ui.value;
+ $scope.dialogChart.rateWindow = ui.value * 1000;
+ $scope.$apply();
+ if (dialogSvgChart)
+ dialogSvgChart.tick($scope.svgDivId);
+ }
+ });
+ } else {
+ setTimeout(initRateSlider, 100);
+ }
+ };
+ //initRateSlider();
+
+ var initDurationSlider = function () {
+ if (document.getElementById('durationSlider')) {
+ $( '#durationSlider' ).slider({
+ value: $scope.dialogChart.visibleDuration,
+ min: 0.25,
+ max: 10,
+ step: 0.25,
+ slide: function( event, ui ) {
+ $scope.visibleDuration = $scope.dialogChart.visibleDuration = ui.value;
+ $scope.$apply();
+ if (dialogSvgChart)
+ dialogSvgChart.tick($scope.svgDivId);
+ }
+ });
+ } else {
+ setTimeout(initDurationSlider, 100);
+ }
+ };
+ initDurationSlider();
+
+ $scope.adding = function () {
+ return adding;
+ };
+
+ $scope.isOnChartsPage = function () {
+ let chart = $scope.chart;
+ if (adding)
+ return QDRChartService.isAttrCharted(chart.nodeId(), chart.entity(), chart.name(), chart.attr(), chart.aggregate());
+ else
+ return $scope.chart.dashboard;
+ };
+
+ // handle the Apply button click
+ // update the dashboard chart's properties
+ $scope.apply = function () {
+ $scope.chart.areaColor = $scope.dialogChart.areaColor;
+ $scope.chart.lineColor = $scope.dialogChart.lineColor;
+ $scope.chart.type = $scope.dialogChart.type;
+ $scope.chart.rateWindow = $scope.rateWindow * 1000;
+ $scope.chart.title($scope.dialogChart.title());
+ $scope.chart.visibleDuration = $scope.dialogChart.visibleDuration;
+ QDRChartService.saveCharts();
+ if (typeof updateTick === 'function')
+ updateTick();
+ };
+
+ // add a new chart to the dashboard based on the current dialog settings
+ $scope.copyToDashboard = function () {
+ let chart = $scope.dialogChart.copy();
+ // set the new chart's dashboard state
+ QDRChartService.addDashboard(chart);
+ // notify the chart controller that it needs to display a new chart
+ dashboard.addChart(chart);
+ };
+
+ // update the chart on the popup dialog
+ var updateDialogChart = function () {
+ // draw the chart using the current data
+ if (dialogSvgChart)
+ dialogSvgChart.tick($scope.svgDivId);
+
+ // draw the chart again in 1 second
+ const updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 1000;
+ if (updateTimer)
+ clearTimeout(updateTimer);
+ updateTimer = setTimeout(updateDialogChart, updateRate);
+ };
+
+ var showChart = function () {
+ // ensure the div for our chart is loaded in the dom
+ let div = angular.element('#' + $scope.svgDivId);
+ if (!div.width()) {
+ setTimeout(showChart, 100);
+ return;
+ }
+ dialogSvgChart = new QDRChartService.pfAreaChart($scope.dialogChart, $scope.svgDivId);
+ /*
+ $('input[name=areaColor]').val($scope.dialogChart.areaColor);
+ $('input[name=areaColor]').on('input', function (e) {
+ $scope.dialogChart.areaColor = $(this).val();
+ updateDialogChart()
+ })
+*/
+ if (updateTimer)
+ clearTimeout(updateTimer);
+ updateDialogChart();
+ };
+ showChart();
+ });
+ return QDR;
+
+} (QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrChartService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrChartService.js b/console/stand-alone/plugin/js/qdrChartService.js
new file mode 100644
index 0000000..574ea07
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrChartService.js
@@ -0,0 +1,859 @@
+/*
+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.
+*/
+'use strict';
+/* global angular d3 c3 */
+
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ // The QDR chart service handles periodic gathering data for charts and displaying the charts
+ QDR.module.factory('QDRChartService', ['QDRService',
+ function(QDRService) {
+
+ let instance = 0; // counter for chart instances
+ let bases = [];
+ var findBase = function(name, attr, request) {
+ for (let i = 0; i < bases.length; ++i) {
+ let base = bases[i];
+ if (base.equals(name, attr, request))
+ return base;
+ }
+ return null;
+ };
+
+ function ChartBase(name, attr, request) {
+ // the base chart attributes
+ this.name = name; // the record's "name" field
+ this.attr = attr; // the record's attr field to chart
+ this.request = request; // the associated request that fetches the data
+
+ // copy the savable properties to an object
+ this.copyProps = function(o) {
+ o.name = this.name;
+ o.attr = this.attr;
+ this.request.copyProps(o);
+ };
+
+ this.equals = function(name, attr, request) {
+ return (this.name == name && this.attr == attr && this.request.equals(request));
+ };
+ }
+
+ // Object that represents a visible chart
+ // There can be multiple of these per ChartBase (eg. one rate and one value chart)
+ function Chart(opts, request) { //name, attr, cinstance, request) {
+
+ let base = findBase(opts.name, opts.attr, request);
+ if (!base) {
+ base = new ChartBase(opts.name, opts.attr, request);
+ bases.push(base);
+ }
+ this.base = base;
+ this.instance = angular.isDefined(opts.instance) ? opts.instance : ++instance;
+ this.dashboard = false; // is this chart on the dashboard page
+ this.hdash = false; // is this chart on the hawtio dashboard page
+ this.hreq = false; // has this hdash chart been requested
+ this.type = opts.type ? opts.type : 'value'; // value or rate
+ this.rateWindow = opts.rateWindow ? opts.rateWindow : 1000; // calculate the rate of change over this time interval. higher == smother graph
+ this.areaColor = '#32b9f3'; // the chart's area color when not an empty string
+ this.lineColor = '#058dc7'; // the chart's line color when not an empty string
+ this.visibleDuration = opts.visibleDuration ? opts.visibleDuration : opts.type === 'rate' ? 0.25 : 1; // number of minutes of data to show (<= base.duration)
+ this.userTitle = null; // user title overrides title()
+ this.hideLabel = opts.hideLabel;
+ this.hideLegend = opts.hideLegend;
+
+ // generate a unique id for this chart
+ this.id = function() {
+ let name = this.name();
+ let nameparts = name.split('/');
+ if (nameparts.length == 2)
+ name = nameparts[1];
+ let key = QDRService.management.topology.nameFromId(this.request().nodeId) + this.request().entity + name + this.attr() + '_' + this.instance + '_' + (this.request().aggregate ? '1' : '0');
+ // remove all characters except letters,numbers, and _
+ return key.replace(/[^\w]/gi, '');
+ };
+ // copy the savable properties to an object
+ this.copyProps = function(o) {
+ o.type = this.type;
+ o.rateWindow = this.rateWindow;
+ o.areaColor = this.areaColor;
+ o.lineColor = this.lineColor;
+ o.visibleDuration = this.visibleDuration;
+ o.userTitle = this.userTitle;
+ o.dashboard = this.dashboard;
+ o.hdash = this.hdash;
+ o.instance = this.instance;
+ this.base.copyProps(o);
+ };
+ this.name = function(_) {
+ if (!arguments.length) return this.base.name;
+ this.base.name = _;
+ return this;
+ };
+ this.attr = function(_) {
+ if (!arguments.length) return this.base.attr;
+ this.base.attr = _;
+ return this;
+ };
+ this.nodeId = function(_) {
+ if (!arguments.length) return this.base.request.nodeId;
+ this.base.request.nodeId = _;
+ return this;
+ };
+ this.entity = function(_) {
+ if (!arguments.length) return this.base.request.entity;
+ this.base.request.entity = _;
+ return this;
+ };
+ this.aggregate = function(_) {
+ if (!arguments.length) return this.base.request.aggregate;
+ this.base.request.aggregate = _;
+ return this;
+ };
+ this.request = function(_) {
+ if (!arguments.length) return this.base.request;
+ this.base.request = _;
+ return this;
+ };
+ this.data = function() {
+ return this.base.request.data(this.base.name, this.base.attr); // refernce to chart's data array
+ };
+ this.interval = function(_) {
+ if (!arguments.length) return this.base.request.interval;
+ this.base.request.interval = _;
+ return this;
+ };
+ this.duration = function(_) {
+ if (!arguments.length) return this.base.request.duration;
+ this.base.request.duration = _;
+ return this;
+ };
+ this.router = function () {
+ return QDRService.management.topology.nameFromId(this.nodeId());
+ };
+ this.title = function(_) {
+ let name = this.request().aggregate ? 'Aggregate' : QDRService.management.topology.nameFromId(this.nodeId());
+ let computed = name +
+ ' ' + QDRService.utilities.humanify(this.attr()) +
+ ' - ' + this.name();
+ if (!arguments.length) return this.userTitle || computed;
+
+ // don't store computed title in userTitle
+ if (_ === computed)
+ _ = null;
+ this.userTitle = _;
+ return this;
+ };
+ this.title_short = function() {
+ if (!arguments.length) return this.userTitle || this.name();
+ return this;
+ };
+ this.copy = function() {
+ let chart = self.registerChart({
+ nodeId: this.nodeId(),
+ entity: this.entity(),
+ name: this.name(),
+ attr: this.attr(),
+ interval: this.interval(),
+ forceCreate: true,
+ aggregate: this.aggregate(),
+ hdash: this.hdash
+ });
+ chart.type = this.type;
+ chart.areaColor = this.areaColor;
+ chart.lineColor = this.lineColor;
+ chart.rateWindow = this.rateWindow;
+ chart.visibleDuration = this.visibleDuration;
+ chart.userTitle = this.userTitle;
+ return chart;
+ };
+ // compare to a chart
+ this.equals = function(c) {
+ return (c.instance == this.instance &&
+ c.base.equals(this.base.name, this.base.attr, this.base.request) &&
+ c.type == this.type &&
+ c.rateWindow == this.rateWindow &&
+ c.areaColor == this.areaColor &&
+ c.lineColor == this.lineColor);
+ };
+ }
+
+ // Object that represents the management request to fetch and store data for multiple charts
+ function ChartRequest(opts) { //nodeId, entity, name, attr, interval, aggregate) {
+ this.duration = opts.duration || 10; // number of minutes to keep the data
+ this.nodeId = opts.nodeId; // eg amqp:/_topo/0/QDR.A/$management
+ this.entity = opts.entity; // eg .router.address
+ // sorted since the responses will always be sorted
+ this.aggregate = opts.aggregate; // list of nodeIds for aggregate charts
+ this.datum = {}; // object containing array of arrays for each attr
+ // like {attr1: [[date,value],[date,value]...], attr2: [[date,value]...]}
+
+ this.interval = opts.interval || 1000; // number of milliseconds between updates to data
+ this.setTimeoutHandle = null; // used to cancel the next request
+
+ // allow override of normal request's management call to get data
+ this.override = opts.override; // call this instead of internal function to retreive data
+ this.overrideAttrs = opts.overrideAttrs;
+
+ this.data = function(name, attr) {
+ if (this.datum[name] && this.datum[name][attr])
+ return this.datum[name][attr];
+ return null;
+ };
+ this.addAttrName = function(name, attr) {
+ if (Object.keys(this.datum).indexOf(name) == -1) {
+ this.datum[name] = {};
+ }
+ if (Object.keys(this.datum[name]).indexOf(attr) == -1) {
+ this.datum[name][attr] = [];
+ }
+ };
+ this.addAttrName(opts.name, opts.attr);
+
+ this.copyProps = function(o) {
+ o.nodeId = this.nodeId;
+ o.entity = this.entity;
+ o.interval = this.interval;
+ o.aggregate = this.aggregate;
+ o.duration = this.duration;
+ };
+
+ this.removeAttr = function(name, attr) {
+ if (this.datum[name]) {
+ if (this.datum[name][attr]) {
+ delete this.datum[name][attr];
+ }
+ }
+ return this.attrs().length;
+ };
+
+ this.equals = function(r, entity, aggregate) {
+ if (arguments.length == 3) {
+ let o = {
+ nodeId: r,
+ entity: entity,
+ aggregate: aggregate
+ };
+ r = o;
+ }
+ return (this.nodeId === r.nodeId && this.entity === r.entity && this.aggregate == r.aggregate);
+ };
+ this.names = function() {
+ return Object.keys(this.datum);
+ };
+ this.attrs = function() {
+ let attrs = {};
+ Object.keys(this.datum).forEach(function(name) {
+ Object.keys(this.datum[name]).forEach(function(attr) {
+ attrs[attr] = 1;
+ });
+ }, this);
+ return Object.keys(attrs);
+ };
+ }
+
+ // Below here are the properties and methods available on QDRChartService
+ let self = {
+ charts: [], // list of charts to gather data for
+ chartRequests: [], // the management request info (multiple charts can be driven off of a single request
+
+ init: function() {
+ self.loadCharts();
+ QDRService.management.connection.addDisconnectAction(function() {
+ self.charts.forEach(function(chart) {
+ self.unRegisterChart(chart, true);
+ });
+ QDRService.management.connection.addConnectAction(self.init);
+ });
+ },
+
+ findChartRequest: function(nodeId, entity, aggregate) {
+ let ret = null;
+ self.chartRequests.some(function(request) {
+ if (request.equals(nodeId, entity, aggregate)) {
+ ret = request;
+ return true;
+ }
+ });
+ return ret;
+ },
+
+ findCharts: function(opts) { //name, attr, nodeId, entity, hdash) {
+ if (!opts.hdash)
+ opts.hdash = false; // rather than undefined
+ return self.charts.filter(function(chart) {
+ return (chart.name() == opts.name &&
+ chart.attr() == opts.attr &&
+ chart.nodeId() == opts.nodeId &&
+ chart.entity() == opts.entity &&
+ chart.hdash == opts.hdash);
+ });
+ },
+
+ delChartRequest: function(request) {
+ for (let i = 0; i < self.chartRequests.length; ++i) {
+ let r = self.chartRequests[i];
+ if (request.equals(r)) {
+ QDR.log.debug('removed request: ' + request.nodeId + ' ' + request.entity);
+ self.chartRequests.splice(i, 1);
+ self.stopCollecting(request);
+ return;
+ }
+ }
+ },
+
+ delChart: function(chart, skipSave) {
+ let foundBases = 0;
+ for (let i = 0; i < self.charts.length; ++i) {
+ let c = self.charts[i];
+ if (c.base === chart.base)
+ ++foundBases;
+ if (c.equals(chart)) {
+ self.charts.splice(i, 1);
+ if (chart.dashboard && !skipSave)
+ self.saveCharts();
+ }
+ }
+ if (foundBases == 1) {
+ let baseIndex = bases.indexOf(chart.base);
+ bases.splice(baseIndex, 1);
+ }
+ },
+
+ createChart: function (opts, request) {
+ return new Chart(opts, request);
+ },
+ createChartRequest: function (opts) {
+ let request = new ChartRequest(opts); //nodeId, entity, name, attr, interval, aggregate);
+ request.creationTimestamp = opts.now;
+ self.chartRequests.push(request);
+ self.startCollecting(request);
+ self.sendChartRequest(request, true);
+ return request;
+ },
+ destroyChartRequest: function (request) {
+ self.stopCollecting(request);
+ self.delChartRequest(request);
+ },
+
+ registerChart: function(opts) { //nodeId, entity, name, attr, interval, instance, forceCreate, aggregate, hdash) {
+ let request = self.findChartRequest(opts.nodeId, opts.entity, opts.aggregate);
+ if (request) {
+ // add any new attr or name to the list
+ request.addAttrName(opts.name, opts.attr);
+ } else {
+ // the nodeId/entity did not already exist, so add a new request and chart
+ QDR.log.debug('added new request: ' + opts.nodeId + ' ' + opts.entity);
+ request = self.createChartRequest(opts);
+ }
+ let charts = self.findCharts(opts); //name, attr, nodeId, entity, hdash);
+ let chart;
+ if (charts.length == 0 || opts.forceCreate) {
+ if (!opts.use_instance && opts.instance)
+ delete opts.instance;
+ chart = new Chart(opts, request); //opts.name, opts.attr, opts.instance, request);
+ self.charts.push(chart);
+ } else {
+ chart = charts[0];
+ }
+ return chart;
+ },
+
+ // remove the chart for name/attr
+ // if all attrs are gone for this request, remove the request
+ unRegisterChart: function(chart, skipSave) {
+ // remove the chart
+
+ // TODO: how do we remove charts that were added to the hawtio dashboard but then removed?
+ // We don't get a notification that they were removed. Instead, we could just stop sending
+ // the request in the background and only send the request when the chart's tick() event is triggered
+ //if (chart.hdash) {
+ // chart.dashboard = false;
+ // self.saveCharts();
+ // return;
+ //}
+
+ for (let i = 0; i < self.charts.length; ++i) {
+ let c = self.charts[i];
+ if (chart.equals(c)) {
+ let request = chart.request();
+ self.delChart(chart, skipSave);
+ if (request) {
+ // see if any other charts use this attr
+ for (let j = 0; j < self.charts.length; ++j) {
+ let ch = self.charts[j];
+ if (ch.attr() == chart.attr() && ch.request().equals(chart.request()))
+ return;
+ }
+ // no other charts use this attr, so remove it
+ if (request.removeAttr(chart.name(), chart.attr()) == 0) {
+ self.destroyChartRequest(request);
+ }
+ }
+ }
+ }
+ if (!skipSave)
+ self.saveCharts();
+ },
+
+ stopCollecting: function(request) {
+ if (request.setTimeoutHandle) {
+ clearInterval(request.setTimeoutHandle);
+ request.setTimeoutHandle = null;
+ }
+ },
+
+ startCollecting: function(request) {
+ request.setTimeoutHandle = setInterval(self.sendChartRequest, request.interval, request);
+ },
+ shouldRequest: function() {
+ // see if any of the charts associated with this request have either dialog, dashboard, or hreq
+ return self.charts.some(function(chart) {
+ return (chart.dashboard || chart.hreq) || (!chart.dashboard && !chart.hdash);
+ });
+ },
+ // send the request
+ sendChartRequest: function(request) {
+ if (request.busy)
+ return;
+ if (self.charts.length > 0 && !self.shouldRequest(request)) {
+ return;
+ }
+ // ensure the response has the name field so we can associate the response values with the correct chart
+ let attrs = request.attrs();
+ if (attrs.indexOf('name') == -1)
+ attrs.push('name');
+
+ // this is called when the response is received
+ var saveResponse = function(nodeId, entity, response) {
+ request.busy = false;
+ if (!response || !response.attributeNames)
+ return;
+ //QDR.log.debug("got chart results for " + nodeId + " " + entity);
+ // records is an array that has data for all names
+ let records = response.results;
+ if (!records)
+ return;
+
+ let now = new Date();
+ let cutOff = new Date(now.getTime() - request.duration * 60 * 1000);
+ // index of the "name" attr in the response
+ let nameIndex = response.attributeNames.indexOf('name');
+ if (nameIndex < 0)
+ return;
+
+ let names = request.names();
+ // for each record returned, find the name/attr for this request and save the data with this timestamp
+ for (let i = 0; i < records.length; ++i) {
+ let name = records[i][nameIndex];
+ // if we want to store the values for some attrs for this name
+ if (names.indexOf(name) > -1) {
+ attrs.forEach(function(attr) {
+ let attrIndex = response.attributeNames.indexOf(attr);
+ if (records[i][attrIndex] !== undefined) {
+ let data = request.data(name, attr); // get a reference to the data array
+ if (data) {
+
+ if (request.aggregate) {
+ data.push([now, response.aggregates[i][attrIndex].sum, response.aggregates[i][attrIndex].detail]);
+ } else {
+ data.push([now, records[i][attrIndex]]);
+ }
+ // expire the old data
+ while (data[0][0] < cutOff) {
+ data.shift();
+ }
+ }
+ }
+ });
+ }
+ }
+ };
+ request.busy = true;
+ // check for override of request
+ if (request.override) {
+ request.override(request, saveResponse);
+ } else {
+ // send the appropriate request
+ if (request.aggregate) {
+ let nodeList = QDRService.management.topology.nodeIdList();
+ QDRService.management.topology.getMultipleNodeInfo(nodeList, request.entity, attrs, saveResponse, request.nodeId);
+ } else {
+ QDRService.management.topology.fetchEntity(request.nodeId, request.entity, attrs, saveResponse);
+ }
+ }
+ },
+
+ numCharts: function() {
+ return self.charts.filter(function(chart) {
+ return chart.dashboard;
+ }).length;
+ //return self.charts.length;
+ },
+
+ isAttrCharted: function(nodeId, entity, name, attr, aggregate) {
+ let charts = self.findCharts({
+ name: name,
+ attr: attr,
+ nodeId: nodeId,
+ entity: entity
+ });
+ // if any of the matching charts are on the dashboard page, return true
+ return charts.some(function(chart) {
+ return (chart.dashboard && (aggregate ? chart.aggregate() : !chart.aggregate()));
+ });
+ },
+
+ addHDash: function(chart) {
+ chart.hdash = true;
+ self.saveCharts();
+ },
+ delHDash: function(chart) {
+ chart.hdash = false;
+ self.saveCharts();
+ },
+ addDashboard: function(chart) {
+ chart.dashboard = true;
+ self.saveCharts();
+ },
+ delDashboard: function(chart) {
+ chart.dashboard = false;
+ self.saveCharts();
+ },
+ // save the charts to local storage
+ saveCharts: function() {
+ let minCharts = [];
+
+ self.charts.forEach(function(chart) {
+ let minChart = {};
+ // don't save chart unless it is on the dashboard
+ if (chart.dashboard || chart.hdash) {
+ chart.copyProps(minChart);
+ minCharts.push(minChart);
+ }
+ });
+ localStorage['QDRCharts'] = angular.toJson(minCharts);
+ },
+ loadCharts: function() {
+ let charts = angular.fromJson(localStorage['QDRCharts']);
+ if (charts) {
+ // get array of known ids
+ let nodeList = QDRService.management.topology.nodeIdList();
+ charts.forEach(function(chart) {
+ // if this chart is not in the current list of nodes, skip
+ if (nodeList.indexOf(chart.nodeId) >= 0) {
+ if (!angular.isDefined(chart.instance)) {
+ chart.instance = ++instance;
+ }
+ if (chart.instance >= instance)
+ instance = chart.instance + 1;
+ if (!chart.duration)
+ chart.duration = 1;
+ if (chart.nodeList)
+ chart.aggregate = true;
+ if (!chart.hdash)
+ chart.hdash = false;
+ if (!chart.dashboard)
+ chart.dashboard = false;
+ if (!chart.hdash && !chart.dashboard)
+ chart.dashboard = true;
+ if (chart.hdash && chart.dashboard)
+ chart.dashboard = false;
+ chart.forceCreate = true;
+ chart.use_instance = true;
+ let newChart = self.registerChart(chart); //chart.nodeId, chart.entity, chart.name, chart.attr, chart.interval, true, chart.aggregate);
+ newChart.dashboard = chart.dashboard;
+ newChart.hdash = chart.hdash;
+ newChart.hreq = false;
+ newChart.type = chart.type;
+ newChart.rateWindow = chart.rateWindow;
+ newChart.areaColor = chart.areaColor ? chart.areaColor : '#32b9f3';
+ newChart.lineColor = chart.lineColor ? chart.lineColor : '#058dc7';
+ newChart.duration(chart.duration);
+ newChart.visibleDuration = chart.visibleDuration ? chart.visibleDuration : newChart.type === 'rate' ? 0.25 : 1;
+ if (chart.userTitle)
+ newChart.title(chart.userTitle);
+ }
+ });
+ }
+ },
+
+ // constructor for a c3 area chart
+ pfAreaChart: function (chart, chartId, defer, width) {
+ if (!chart)
+ return;
+
+ // reference to underlying chart
+ this.chart = chart;
+
+ // if this is an aggregate chart, show it stacked
+ this.stacked = chart.request().aggregate;
+
+ // the id of the html element that is bound to the chart. The svg will be a child of this
+ this.htmlId = chartId;
+
+ // an array of 20 colors
+ this.colors = d3.scale.category10().range();
+
+ if (!defer)
+ this.generate(width);
+ },
+
+ // aggregate chart is based on pfAreaChart
+ pfAggChart: function (chart, chartId, defer) {
+ // inherit pfChart's properties, but force a defer
+ self.pfAreaChart.call(this, chart, chartId, true);
+
+ // the request is for aggregate data, but the chart is for the sum and not the detail
+ // Explanation: When the chart.request is aggregate, each data point is composed of 3 parts:
+ // 1. the datetime stamp
+ // 2. the sum of the value for all routers
+ // 3. an object with each router's name and value for this data point
+ // Normally, an aggregate chart shows lines for each of the routers and ignores the sum
+ // For this chart, we want to chart the sum (the 2nd value), so we set stacked to false
+ this.stacked = false;
+
+ // let chart legends and tooltips show 'Total' instead of a router name
+ this.aggregate = true;
+
+ if (!defer)
+ this.generate();
+ }
+ };
+ // allow pfAggChart to inherit prototyped methods
+ self.pfAggChart.prototype = Object.create(self.pfAreaChart.prototype);
+ // except for the constructor
+ self.pfAggChart.prototype.constructor = self.pfAggChart;
+
+ // create the svg and bind it to the given div.id
+ self.pfAreaChart.prototype.generate = function (width) {
+ let chart = this.chart; // for access during chart callbacks
+ let self = this;
+
+ // list of router names. used to get the color index
+ let nameList = QDRService.management.topology.nodeNameList();
+
+ let c3ChartDefaults = $().c3ChartDefaults();
+ let singleAreaChartConfig = c3ChartDefaults.getDefaultSingleAreaConfig();
+ singleAreaChartConfig.bindto = '#' + this.htmlId;
+ singleAreaChartConfig.size = {
+ width: width || 400,
+ height: 200
+ };
+ singleAreaChartConfig.data = {
+ x: 'x', // x-axis is named x
+ columns: [[]],
+ type: 'area-spline'
+ };
+ singleAreaChartConfig.axis = {
+ x: {
+ type: 'timeseries',
+ tick: {
+ format: (function (d) {
+ let data = this.singleAreaChart.data.shown();
+ let first = data[0]['values'][0].x;
+
+ if (d - first == 0) {
+ return d3.timeFormat('%I:%M:%S')(d);
+ }
+ return d3.timeFormat('%M:%S')(d);
+ }).bind(this),
+ culling: {max: 4}
+ }
+ },
+ y: {
+ tick: {
+ format: function (d) { return d<1 ? d3.format('.2f')(d) : d3.format('.2s')(d); },
+ count: 5
+ }
+ }
+ };
+
+ if (!chart.hideLabel) {
+ singleAreaChartConfig.axis.x.label = {
+ text: chart.name(),
+ position: 'outer-right'
+ };
+
+ }
+ singleAreaChartConfig.transition = {
+ duration: 0
+ };
+
+ singleAreaChartConfig.area = {
+ zerobased: false
+ };
+
+ singleAreaChartConfig.tooltip = {
+ contents: function (d) {
+ let d3f = ',';
+ if (chart.type === 'rate')
+ d3f = ',.2f';
+ let zPre = function (i) {
+ if (i < 10) {
+ i = '0' + i;
+ }
+ return i;
+ };
+ let h = zPre(d[0].x.getHours());
+ let m = zPre(d[0].x.getMinutes());
+ let s = zPre(d[0].x.getSeconds());
+ let table = '<table class=\'dispatch-c3-tooltip\'> <tr><th colspan=\'2\' class=\'text-center\'><strong>'+h+':'+m+':'+s+'</strong></th></tr> <tbody>';
+ for (let i=0; i<d.length; i++) {
+ let colorIndex = nameList.indexOf(d[i].id) % 10;
+ let span = '<span class=\'chart-tip-legend\' style=\'background-color: '+self.colors[colorIndex]+';\'> </span>' + d[i].id;
+ table += ('<tr><td>'+span+'<td>'+d3.format(d3f)(d[i].value)+'</td></tr>');
+ }
+ table += '</tbody></table>';
+ return table;
+ }
+ };
+
+ singleAreaChartConfig.title = {
+ text: QDRService.utilities.humanify(this.chart.attr())
+ };
+
+ singleAreaChartConfig.data.colors = {};
+ nameList.forEach( (function (r, i) {
+ singleAreaChartConfig.data.colors[r] = this.colors[i % 10];
+ }).bind(this));
+
+ singleAreaChartConfig.data.color = (function (color, d) {
+ let i = nameList.indexOf(d);
+ return i >= 0 ? this.colors[i % 10] : color;
+ }).bind(this);
+
+ if (!chart.hideLegend) {
+ singleAreaChartConfig.legend = {
+ show: true,
+ };
+ }
+
+ if (this.stacked) {
+ // create a stacked area chart
+ singleAreaChartConfig.data.groups = [QDRService.management.topology.nodeNameList()];
+ singleAreaChartConfig.data.order = function (t1, t2) { return t1.id < t2.id; };
+ }
+
+ this.singleAreaChart = c3.generate(singleAreaChartConfig);
+ };
+
+ // filter/modify the chart.data into data points for the svg
+ /* the collected data looks like:
+ [[date, val, [v1,v2,...]], [date, val, [v1,v2,...]],...]
+ with date being the timestamp of the sample
+ val being the total value
+ and the [v1,v2,...] array being the component values for each router for stacked charts
+
+ for stacked charts, the returned data looks like:
+ [['x', date, date,...},
+ ['R1', v1, v1,...},
+ ['R2', v2, v2,...],
+ ...]
+
+ for non-stacked charts, the returned data looks like:
+ ['x', date, date,...],
+ ['R1', val, val,...]]
+
+ for rate charts, all the values returned are the change per second between adjacent values
+ */
+ self.pfAreaChart.prototype.chartData = function() {
+ let data = this.chart.data();
+ let nodeList = QDRService.management.topology.nodeNameList();
+
+ // oldest data point that should be visible
+ let now = new Date();
+ let visibleDate = new Date(now.getTime() - this.chart.visibleDuration * 60 * 1000);
+
+ let accessorSingle = function (d, d1, elapsed) {
+ return this.chart.type === 'rate' ? (d1[1] - d[1]) / elapsed : d[1];
+ };
+ let accessorStacked = function (d, d1, elapsed, i) {
+ return this.chart.type === 'rate' ? (d1[2][i].val - d[2][i].val) / elapsed : d[2][i].val;
+ };
+ let accessor = this.stacked ? accessorStacked : accessorSingle;
+
+ let dx = ['x'];
+ let dlines = [];
+ if (this.stacked) {
+ // for stacked, there is a line per router
+ nodeList.forEach( function (node) {
+ dlines.push([node]);
+ });
+ } else {
+ // for non-stacked, there is only one line
+ dlines.push([this.aggregate ? 'Total' : this.chart.router()]);
+ }
+ for (let i=0; i<data.length; i++) {
+ let d = data[i], elapsed = 1, d1;
+ if (d[0] >= visibleDate) {
+ if (this.chart.type === 'rate' && i < data.length-1) {
+ d1 = data[i+1];
+ elapsed = Math.max((d1[0] - d[0]) / 1000, 0.001); // number of seconds that elapsed
+ }
+ // don't push the last data point for a rate chart
+ if (this.chart.type !== 'rate' || i < data.length-1) {
+ dx.push(d[0]);
+ if (this.stacked) {
+ for (let nodeIndex=0; nodeIndex<nodeList.length; nodeIndex++) {
+ dlines[nodeIndex].push(accessor.call(this, d, d1, elapsed, nodeIndex));
+ }
+ } else {
+ dlines[0].push(accessor.call(this, d, d1, elapsed));
+ }
+ }
+ }
+ }
+ let columns = [dx];
+ dlines.forEach( function (line) {
+ columns.push(line);
+ });
+ return columns;
+ };
+
+ // get the data for the chart and update it
+ self.pfAreaChart.prototype.tick = function() {
+ // can't draw charts that don't have data yet
+ if (!this.chart.data() || this.chart.data().length == 0 || !this.singleAreaChart) {
+ return;
+ }
+
+ // update the chart title
+ // since there is no c3 api to get or set the chart title, we change the title directly using d3
+ let rate = '';
+ if (this.chart.type === 'rate')
+ rate = ' per second';
+ d3.select('#'+this.htmlId+' svg text.c3-title').text(QDRService.utilities.humanify(this.chart.attr()) + rate);
+
+ let d = this.chartData();
+ // load the new data
+ // using the c3.flow api causes the x-axis labels to jump around
+ this.singleAreaChart.load({
+ columns: d
+ });
+ };
+
+ return self;
+ }
+ ]);
+
+ return QDR;
+}(QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrCharts.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrCharts.js b/console/stand-alone/plugin/js/qdrCharts.js
new file mode 100644
index 0000000..b296de7
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrCharts.js
@@ -0,0 +1,160 @@
+/*
+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.
+*/
+'use strict';
+
+/* global angular */
+
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+ /**
+ * @method ChartsController
+ *
+ * Controller that handles the QDR charts page
+ */
+ QDR.module.controller('QDR.ChartsController', function($scope, QDRService, QDRChartService, $uibModal, $location, $routeParams, $timeout) {
+
+ let updateTimer = null;
+
+ if (!QDRService.management.connection.is_connected()) {
+ // we are not connected. we probably got here from a bookmark or manual page reload
+ QDR.redirectWhenConnected($location, 'charts');
+ return;
+ }
+
+ $scope.svgCharts = [];
+ // create an svg object for each chart
+ QDRChartService.charts.filter(function (chart) {return chart.dashboard;}).forEach(function (chart) {
+ let svgChart = new QDRChartService.pfAreaChart(chart, chart.id(), true);
+ svgChart.zoomed = false;
+ $scope.svgCharts.push(svgChart);
+ });
+
+
+ // redraw the chart every update period
+ var updateCharts = function () {
+ $scope.svgCharts.forEach(function (svgChart) {
+ svgChart.tick(svgChart.chart.id()); // on this page we are using the chart.id() as the div id in which to render the chart
+ });
+ const updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 1000;
+ if (updateTimer) {
+ clearTimeout(updateTimer);
+ }
+ updateTimer = setTimeout(updateCharts, updateRate);
+ };
+
+ // called by ng-init in the html when the page is loaded
+ $scope.chartsLoaded = function () {
+ // ensure the div for our chart is loaded in the dom
+ let div = angular.element('.chartContainer');
+ if (!div.width()) {
+ setTimeout($scope.chartsLoaded, 100);
+ return;
+ }
+ // create an svg object for each chart
+ $scope.svgCharts.forEach ( function (c) {
+ c.generate(380);
+ QDRChartService.sendChartRequest(c.chart.request(), true);
+ });
+ if (updateTimer)
+ clearTimeout(updateTimer);
+ setTimeout(updateCharts);
+ };
+
+ $scope.zoomChart = function (chart) {
+ chart.zoomed = !chart.zoomed;
+ chart.zoom(chart.chart.id(), chart.zoomed);
+ };
+ $scope.showListPage = function () {
+ $location.path('/list');
+ };
+
+ $scope.hasCharts = function () {
+ return QDRChartService.numCharts() > 0;
+ };
+
+ $scope.editChart = function (chart) {
+ doDialog('tmplChartConfig.html', chart.chart);
+ };
+
+ $scope.delChart = function (chart) {
+ QDRChartService.unRegisterChart(chart.chart);
+ // remove from svgCharts
+ $scope.svgCharts.forEach(function (svgChart, i) {
+ if (svgChart === chart) {
+ delete $scope.svgCharts.splice(i, 1);
+ }
+ });
+ };
+
+ // called from dialog when we want to clone the dialog chart
+ // the chart argument here is a QDRChartService chart
+ $scope.addChart = function (chart) {
+ let nchart = new QDRChartService.pfAreaChart(chart, chart.id(), true);
+ $scope.svgCharts.push(nchart);
+ $timeout( function () {
+ nchart.generate();
+ QDRChartService.sendChartRequest(chart.request(), true);
+ });
+ };
+
+ $scope.$on('$destroy', function() {
+ if (updateTimer) {
+ clearTimeout(updateTimer);
+ updateTimer = null;
+ }
+ for (let i=$scope.svgCharts.length-1; i>=0; --i) {
+ delete $scope.svgCharts.splice(i, 1);
+ }
+ });
+
+ function doDialog(template, chart) {
+
+ $uibModal.open({
+ backdrop: true,
+ keyboard: true,
+ backdropClick: true,
+ templateUrl: QDR.templatePath + template,
+ controller: 'QDR.ChartDialogController',
+ resolve: {
+ chart: function() {
+ return chart;
+ },
+ updateTick: function () {
+ return function () { return updateCharts; };
+ },
+ dashboard: function () {
+ return $scope;
+ },
+ adding: function () {
+ return false;
+ }
+ }
+ });
+ }
+
+ });
+
+
+ return QDR;
+
+}(QDR || {}));
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrGlobals.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrGlobals.js b/console/stand-alone/plugin/js/qdrGlobals.js
new file mode 100644
index 0000000..af06c55
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrGlobals.js
@@ -0,0 +1,46 @@
+/*
+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.
+*/
+'use strict';
+
+var QDR = (function(QDR) {
+
+ QDR.Folder = (function () {
+ function Folder(title) {
+ this.title = title;
+ this.children = [];
+ this.folder = true;
+ }
+ return Folder;
+ })();
+ QDR.Leaf = (function () {
+ function Leaf(title) {
+ this.title = title;
+ }
+ return Leaf;
+ })();
+
+ QDR.Core = {
+ notification: function (severity, msg) {
+ $.notify(msg, severity);
+ }
+ };
+
+ return QDR;
+
+} (QDR || {}));
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[4/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js
files that were in the npm repository
Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrList.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrList.js b/console/stand-alone/plugin/js/qdrList.js
new file mode 100644
index 0000000..056e686
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrList.js
@@ -0,0 +1,938 @@
+/*
+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.
+*/
+'use strict';
+/* global angular d3 */
+
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ /**
+ * Controller for the main interface
+ */
+ QDR.module.controller('QDR.ListController', ['$scope', '$location', '$uibModal', '$filter', '$timeout', 'QDRService', 'QDRChartService', 'uiGridConstants', '$sce',
+ function ($scope, $location, $uibModal, $filter, $timeout, QDRService, QDRChartService, uiGridConstants, $sce) {
+
+ QDR.log.debug('QDR.ListControll started with location of ' + $location.path() + ' and connection of ' + QDRService.management.connection.is_connected());
+ let updateIntervalHandle = undefined,
+ updateInterval = 5000,
+ last_updated = 0,
+ updateNow = false,
+ ListExpandedKey = 'QDRListExpanded',
+ SelectedEntityKey = 'QDRSelectedEntity',
+ ActivatedKey = 'QDRActivatedKey';
+ $scope.details = {};
+
+ $scope.tmplListTree = QDR.templatePath + 'tmplListTree.html';
+ $scope.selectedEntity = localStorage[SelectedEntityKey] || 'address';
+ $scope.ActivatedKey = localStorage[ActivatedKey] || null;
+ if ($scope.selectedEntity == 'undefined')
+ $scope.selectedEntity = undefined;
+ $scope.selectedNode = localStorage['QDRSelectedNode'];
+ $scope.selectedNodeId = localStorage['QDRSelectedNodeId'];
+ $scope.selectedRecordName = localStorage['QDRSelectedRecordName'];
+ $scope.nodes = [];
+ $scope.currentNode = undefined;
+ $scope.modes = [
+ {
+ content: '<a><i class="icon-list"></i> Attributes</a>',
+ id: 'attributes',
+ op: 'READ',
+ title: 'View router attributes',
+ isValid: function () { return true; }
+ },
+ {
+ content: '<a><i class="icon-edit"></i> Update</a>',
+ id: 'operations',
+ op: 'UPDATE',
+ title: 'Update this attribute',
+ isValid: function () {
+ return $scope.operations.indexOf(this.op) > -1;
+ }
+ },
+ {
+ content: '<a><i class="icon-plus"></i> Create</a>',
+ id: 'operations',
+ op: 'CREATE',
+ title: 'Create a new attribute',
+ isValid: function () { return $scope.operations.indexOf(this.op) > -1; }
+ },
+ {
+ content: '<a><i class="icon-remove"></i> Delete</a>',
+ id: 'delete',
+ op: 'DELETE',
+ title: 'Delete',
+ isValid: function () { return $scope.operations.indexOf(this.op) > -1; }
+ },
+ {
+ content: '<a><i class="icon-eye-open"></i> Fetch</a>',
+ id: 'log',
+ op: 'GET-LOG',
+ title: 'Fetch recent log entries',
+ isValid: function () { return ($scope.selectedEntity === 'log'); }
+ }
+ ];
+ $scope.operations = [];
+ $scope.currentMode = $scope.modes[0];
+ $scope.isModeSelected = function (mode) {
+ return mode === $scope.currentMode;
+ };
+ $scope.fetchingLog = false;
+ $scope.selectMode = function (mode) {
+ $scope.currentMode = mode;
+ if (mode.id === 'log') {
+ $scope.logResults = [];
+ $scope.fetchingLog = true;
+ let entity; // undefined since it is not supported in the GET-LOG call
+ QDRService.management.connection.sendMethod($scope.currentNode.id, entity, {}, $scope.currentMode.op)
+ .then( function (response) {
+ let statusCode = response.context.message.application_properties.statusCode;
+ if (statusCode < 200 || statusCode >= 300) {
+ QDR.Core.notification('error', response.context.message.statusDescription);
+ QDR.log.error('Error ' + response.context.message.statusDescription);
+ return;
+ }
+ $timeout( function () {
+ $scope.fetchingLog = false;
+ $scope.logResults = response.response.filter( function (entry) {
+ return entry[0] === $scope.detailsObject.module;
+ }).sort( function (a, b) {
+ return b[5] - a[5];
+ }).map( function (entry) {
+ return {
+ type: entry[1],
+ message: entry[2],
+ source: entry[3],
+ line: entry[4],
+ time: Date(entry[5]).toString()
+ };
+ });
+ });
+ });
+ }
+ };
+ $scope.isValid = function (mode) {
+ return mode.isValid();
+ };
+
+ $scope.expandAll = function () {
+ $('#entityTree').fancytree('getTree').visit(function(node){
+ node.setExpanded(true);
+ });
+ };
+ $scope.contractAll = function () {
+ $('#entityTree').fancytree('getTree').visit(function(node){
+ node.setExpanded(false);
+ });
+ };
+
+ if (!QDRService.management.connection.is_connected()) {
+ // we are not connected. we probably got here from a bookmark or manual page reload
+ QDR.redirectWhenConnected($location, 'list');
+ return;
+ }
+
+ let excludedEntities = ['management', 'org.amqp.management', 'operationalEntity', 'entity', 'configurationEntity', 'dummy', 'console'];
+ let aggregateEntities = ['router.address'];
+
+ let classOverrides = {
+ 'connection': function (row, nodeId) {
+ let isConsole = QDRService.utilities.isAConsole (row.properties.value, row.identity.value, row.role.value, nodeId);
+ return isConsole ? 'console' : row.role.value === 'inter-router' ? 'inter-router' : 'external';
+ },
+ 'router.link': function (row, nodeId) {
+ let link = {nodeId: nodeId, connectionId: row.connectionId.value};
+
+ let isConsole = QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link));
+ return isConsole ? 'console' : row.linkType.value;
+ },
+ 'router.address': function (row) {
+ let identity = QDRService.utilities.identity_clean(row.identity.value);
+ let address = QDRService.utilities.addr_text(identity);
+ let cls = QDRService.utilities.addr_class(identity);
+ if (address === '$management')
+ cls = 'internal ' + cls;
+ return cls;
+ }
+ };
+
+ var lookupOperations = function () {
+ let ops = QDRService.management.schema().entityTypes[$scope.selectedEntity].operations.filter( function (op) { return op !== 'READ';});
+ $scope.operation = ops.length ? ops[0] : '';
+ return ops;
+ };
+ let entityTreeChildren = [];
+ let expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
+ let saveExpanded = function () {
+ // save the list of entities that are expanded
+ let tree = $('#entityTree').fancytree('getTree');
+ let list = [];
+ tree.visit( function (tnode) {
+ if (tnode.isExpanded()) {
+ list.push(tnode.key);
+ }
+ });
+ localStorage[ListExpandedKey] = JSON.stringify(list);
+ };
+
+ var onTreeNodeBeforeActivate = function (event, data) {
+ // if node is toplevel entity
+ if (data.node.data.typeName === 'entity') {
+ return false;
+ /*
+ // if the current active node is not this one and not one of its children
+ let active = data.tree.getActiveNode();
+ if (active && !data.node.isActive() && data.node.isExpanded()) { // there is an active node and it's not this one
+ let any = false;
+ let children = data.node.getChildren();
+ if (children) {
+ any = children.some( function (child) {
+ return child.key === active.key;
+ });
+ }
+ if (!any) // none of the clicked on node's children was active
+ return false; // don't activate, just collapse this top level node
+ }
+ */
+ }
+ return true;
+ };
+ var onTreeNodeExpanded = function () {
+ saveExpanded();
+ updateExpandedEntities();
+ };
+ // a tree node was selected
+ var onTreeNodeActivated = function (event, data) {
+ $scope.ActivatedKey = data.node.key;
+ let selectedNode = data.node;
+ $scope.selectedTreeNode = data.node;
+ if ($scope.currentMode.id === 'operations')
+ $scope.currentMode = $scope.modes[0];
+ else if ($scope.currentMode.id === 'log')
+ $scope.selectMode($scope.currentMode);
+ else if ($scope.currentMode.id === 'delete') {
+ // clicked on a tree node while on the delete screen -> switch to attribute screen
+ $scope.currentMode = $scope.modes[0];
+ }
+ if (selectedNode.data.typeName === 'entity') {
+ $scope.selectedEntity = selectedNode.key;
+ $scope.operations = lookupOperations();
+ updateNow = true;
+ } else if (selectedNode.data.typeName === 'attribute') {
+ if (!selectedNode.parent)
+ return;
+ let sameEntity = $scope.selectedEntity === selectedNode.parent.key;
+ $scope.selectedEntity = selectedNode.parent.key;
+ $scope.operations = lookupOperations();
+ $scope.selectedRecordName = selectedNode.key;
+ updateDetails(selectedNode.data.details); // update the table on the right
+ if (!sameEntity) {
+ updateNow = true;
+ }
+ } else if (selectedNode.data.typeName === 'none') {
+ $scope.selectedEntity = selectedNode.parent.key;
+ $scope.selectedRecordName = $scope.selectedEntity;
+ updateDetails(fromSchema($scope.selectedEntity));
+ }
+ };
+ var getExpanded = function (tree) {
+ let list = [];
+ tree.visit( function (tnode) {
+ if (tnode.isExpanded()) {
+ list.push(tnode);
+ }
+ });
+ return list;
+ };
+ // fill in an empty results recoord based on the entities schema
+ var fromSchema = function (entityName) {
+ let row = {};
+ let schemaEntity = QDRService.management.schema().entityTypes[entityName];
+ for (let attr in schemaEntity.attributes) {
+ let entity = schemaEntity.attributes[attr];
+ let value = '';
+ if (angular.isDefined(entity['default'])) {
+ if (entity['type'] === 'integer')
+ value = parseInt(entity['default']); // some default values that are marked as integer are passed as string
+ else
+ value = entity['default'];
+ }
+ row[attr] = {
+ value: value,
+ type: entity.type,
+ graph: false,
+ title: entity.description,
+ aggregate: false,
+ aggregateTip: '',
+ 'default': entity['default']
+ };
+ }
+ return row;
+ };
+ $scope.hasCreate = function () {
+ let schemaEntity = QDRService.management.schema().entityTypes[$scope.selectedEntity];
+ return (schemaEntity.operations.indexOf('CREATE') > -1);
+ };
+
+ var getActiveChild = function (node) {
+ let active = node.children.filter(function (child) {
+ return child.isActive();
+ });
+ if (active.length > 0)
+ return active[0].key;
+ return null;
+ };
+ // the data for the selected entity is available, populate the tree on the left
+ var updateTreeChildren = function (entity, tableRows, expand) {
+ let tree = $('#entityTree').fancytree('getTree'), node, newNode;
+ if (tree && tree.getNodeByKey) {
+ node = tree.getNodeByKey(entity);
+ }
+ if (!tree || !node) {
+ return;
+ }
+ let wasActive = node.isActive();
+ let wasExpanded = node.isExpanded();
+ let activeChildKey = getActiveChild(node);
+ node.removeChildren();
+ if (tableRows.length == 0) {
+ newNode = {
+ extraClasses: 'no-data',
+ typeName: 'none',
+ title: 'no data',
+ key: node.key + '.1'
+ };
+ node.addNode(newNode);
+ if (expand) {
+ updateDetails(fromSchema(entity));
+ $scope.selectedRecordName = entity;
+ }
+ } else {
+ let children = tableRows.map( function (row) {
+ let addClass = entity;
+ if (classOverrides[entity]) {
+ addClass += ' ' + classOverrides[entity](row, $scope.currentNode.id);
+ }
+ let child = {
+ typeName: 'attribute',
+ extraClasses: addClass,
+ tooltip: addClass,
+ key: row.name.value,
+ title: row.name.value,
+ details: row
+ };
+ return child;
+ });
+ node.addNode(children);
+ }
+ // top level node was expanded
+ if (wasExpanded)
+ node.setExpanded(true, {noAnimation: true, noEvents: true, noFocus: true});
+ // if the parent node was active, but none of the children were active, active the 1st child
+ if (wasActive) {
+ if (!activeChildKey) {
+ activeChildKey = node.children[0].key;
+ }
+ }
+ if (!tree.getActiveNode())
+ activeChildKey = $scope.ActivatedKey;
+ // re-active the previously active child node
+ if (activeChildKey) {
+ newNode = tree.getNodeByKey(activeChildKey);
+ // the node may not be there after the update
+ if (newNode)
+ newNode.setActive(true, {noFocus: true}); // fires the onTreeNodeActivated event for this node
+ }
+ //resizer();
+ };
+
+
+ var resizer = function () {
+ // this forces the tree and the grid to be the size of the browser window.
+ // the effect is that the tree and the grid will have vertical scroll bars if needed.
+ // the alternative is to let the tree and grid determine the size of the page and have
+ // the scroll bar on the window
+ // don't allow HTML in the tree titles
+ $('.fancytree-title').each( function () {
+ let unsafe = $(this).html();
+ $(this).html(unsafe.replace(/</g, '<').replace(/>/g, '>'));
+ });
+ let h = $scope.detailFields.length * 30 + 46;
+ $('.ui-grid-viewport').height(h);
+ };
+ $(window).resize(resizer);
+
+ var schemaProps = function (entityName, key, currentNode) {
+ let typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
+
+ let entity = QDRService.management.schema().entityTypes[entityName];
+ let value = entity.attributes[key];
+ // skip identity and depricated fields
+ if (!value)
+ return {input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': ''};
+ let description = value.description || '';
+ let val = value['default'];
+ let disabled = (key == 'identity' || description.startsWith('Deprecated'));
+ // special cases
+ if (entityName == 'log' && key == 'module') {
+ return {input: 'input', type: 'disabled', required: false, selected: '', rawtype: 'string', disabled: true, 'default': ''};
+ }
+ if (entityName === 'linkRoutePattern' && key === 'connector') {
+ // turn input into a select. the values will be populated later
+ value.type = [];
+ // find all the connector names and populate the select
+ QDRService.management.topology.fetchEntity(currentNode.id, 'connector', ['name'], function (nodeName, dotentity, response) {
+ $scope.detailFields.some( function (field) {
+ if (field.name === 'connector') {
+ field.rawtype = response.results.map (function (result) {return result[0];});
+ return true;
+ }
+ });
+ });
+ }
+ return { name: key,
+ humanName: QDRService.utilities.humanify(key),
+ description:value.description,
+ type: disabled ? 'disabled' : 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,
+ disabled: disabled
+ };
+ };
+ $scope.getAttributeValue = function (attribute) {
+ let value = attribute.attributeValue;
+ if ($scope.currentMode.op === 'CREATE' && attribute.name === 'identity')
+ value = '<assigned by system>';
+ return value;
+ };
+
+ // update the table on the right
+ var updateDetails = function (row) {
+ let details = [];
+ $scope.detailsObject = {};
+ let attrs = Object.keys(row).sort();
+ attrs.forEach( function (attr) {
+ let changed = $scope.detailFields.filter(function (old) {
+ return (old.name === attr) ? old.graph && old.rawValue != row[attr].value : false;
+ });
+ let schemaEntity = schemaProps($scope.selectedEntity, attr, $scope.currentNode);
+ details.push( {
+ attributeName: QDRService.utilities.humanify(attr),
+ attributeValue: attr === 'port' ? row[attr].value : QDRService.utilities.pretty(row[attr].value),
+ name: attr,
+ changed: changed.length,
+ rawValue: row[attr].value,
+ graph: row[attr].graph,
+ title: row[attr].title,
+ chartExists: (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr)),
+ aggchartExists: (QDRChartService.isAttrCharted($scope.currentNode.id, $scope.selectedEntity, row.name.value, attr, true)),
+ aggregateValue: QDRService.utilities.pretty(row[attr].aggregate),
+ aggregateTip: row[attr].aggregateTip,
+
+ input: schemaEntity.input,
+ type: schemaEntity.type,
+ required: schemaEntity.required,
+ unique: schemaEntity.unique,
+ selected: schemaEntity.selected,
+ rawtype: schemaEntity.rawtype,
+ disabled: schemaEntity.disabled,
+ 'default': schemaEntity['default']
+ });
+ $scope.detailsObject[attr] = row[attr].value;
+ });
+ $scope.detailFields = details;
+ aggregateColumn();
+ resizer();
+ };
+
+ // called from html ng-style="getTableHeight()"
+ $scope.getTableHeight = function () {
+ return {
+ height: (Math.max($scope.detailFields.length, 15) * 30 + 46) + 'px'
+ };
+ };
+ var updateExpandedEntities = function () {
+ let tree = $('#entityTree').fancytree('getTree');
+ if (tree) {
+ let q = d3.queue(10);
+ let expanded = getExpanded(tree);
+ expanded.forEach( function (node) {
+ q.defer(q_updateTableData, node.key, node.key === $scope.selectedEntity);
+ });
+
+ q.await(function (error) {
+ if (error)
+ QDR.log.error(error.message);
+
+ if (!tree.getActiveNode()) {
+ if ($scope.ActivatedKey) {
+ let node = tree.getNodeByKey($scope.ActivatedKey);
+ if (node) {
+ node.setActive(true, {noEvents: true});
+ }
+ }
+ if (!tree.getActiveNode()) {
+ let first = tree.getFirstChild();
+ if (first) {
+ let child = first.getFirstChild();
+ if (child)
+ first = child;
+ }
+ first.setActive(true);
+ }
+ }
+
+ d3.selectAll('.ui-effects-placeholder').style('height', '0px');
+ resizer();
+
+ last_updated = Date.now();
+ });
+ }
+ };
+
+ // The selection dropdown (list of routers) was changed.
+ $scope.selectNode = function(node) {
+ $scope.selectedNode = node.name;
+ $scope.selectedNodeId = node.id;
+ $timeout( function () {
+ setCurrentNode();
+ updateNow = true;
+ });
+ };
+
+ $scope.$watch('ActivatedKey', function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ localStorage[ActivatedKey] = $scope.ActivatedKey;
+ }
+ });
+ $scope.$watch('selectedEntity', function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
+ $scope.operations = lookupOperations();
+ }
+ });
+ $scope.$watch('selectedNode', function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ localStorage['QDRSelectedNode'] = $scope.selectedNode;
+ localStorage['QDRSelectedNodeId'] = $scope.selectedNodeId;
+ }
+ });
+ $scope.$watch('selectedRecordName', function(newValue, oldValue) {
+ if (newValue != oldValue) {
+ localStorage['QDRSelectedRecordName'] = $scope.selectedRecordName;
+ }
+ });
+
+ /* Called periodically to refresh the data on the page */
+ var q_updateTableData = function (entity, expand, callback) {
+ // don't update the data when on the operations tabs
+ if ($scope.currentMode.id !== 'attributes') {
+ callback(null);
+ return;
+ }
+ var gotNodeInfo = function (nodeName, dotentity, response) {
+ let tableRows = [];
+ let records = response.results;
+ let aggregates = response.aggregates;
+ let attributeNames = response.attributeNames;
+ // If !attributeNmes then there was an error getting the records for this entity
+ if (attributeNames) {
+ let nameIndex = attributeNames.indexOf('name');
+ let identityIndex = attributeNames.indexOf('identity');
+ let ent = QDRService.management.schema().entityTypes[entity];
+ for (let i=0; i<records.length; ++i) {
+ let record = records[i];
+ let aggregate = aggregates ? aggregates[i] : undefined;
+ let row = {};
+ let rowName;
+ if (nameIndex > -1) {
+ rowName = record[nameIndex];
+ if (!rowName && identityIndex > -1) {
+ rowName = record[nameIndex] = (dotentity + '/' + record[identityIndex]);
+ }
+ }
+ if (!rowName) {
+ let msg = 'response attributeNames did not contain a name field';
+ QDR.log.error(msg);
+ callback(Error(msg));
+ return;
+ }
+ for (let j=0; j<attributeNames.length; ++j) {
+ let col = attributeNames[j];
+ row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
+ if (ent) {
+ let att = ent.attributes[col];
+ if (att) {
+ row[col].type = att.type;
+ row[col].graph = att.graph;
+ row[col].title = att.description;
+
+ if (aggregate) {
+ if (att.graph) {
+ row[col].aggregate = att.graph ? aggregate[j].sum : '';
+ let tip = [];
+ aggregate[j].detail.forEach( function (line) {
+ tip.push(line);
+ });
+ row[col].aggregateTip = angular.toJson(tip);
+ }
+ }
+ }
+ }
+ }
+ tableRows.push(row);
+ }
+ tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value); });
+ selectRow({entity: dotentity, rows: tableRows, expand: expand});
+ }
+ callback(null); // let queue handler know we are done
+ };
+ // if this entity should show an aggregate column, send the request to get the info for this entity from all the nedes
+ if (aggregateEntities.indexOf(entity) > -1) {
+ let nodeIdList = QDRService.management.topology.nodeIdList();
+ QDRService.management.topology.getMultipleNodeInfo(nodeIdList, entity, [], gotNodeInfo, $scope.selectedNodeId);
+ } else {
+ QDRService.management.topology.fetchEntity($scope.selectedNodeId, entity, [], gotNodeInfo);
+ }
+ };
+
+ // tableRows are the records that were returned, this populates the left hand tree on the page
+ var selectRow = function (info) {
+ updateTreeChildren(info.entity, info.rows, info.expand);
+ };
+ $scope.detailFields = [];
+
+ $scope.addToGraph = function(rowEntity) {
+ let chart = QDRChartService.registerChart(
+ {nodeId: $scope.selectedNodeId,
+ entity: $scope.selectedEntity,
+ name: $scope.selectedRecordName,
+ attr: rowEntity.name,
+ forceCreate: true});
+
+ doDialog('tmplChartConfig.html', chart);
+ };
+
+ $scope.addAllToGraph = function(rowEntity) {
+ let chart = QDRChartService.registerChart({
+ nodeId: $scope.selectedNodeId,
+ entity: $scope.selectedEntity,
+ name: $scope.selectedRecordName,
+ attr: rowEntity.name,
+ type: 'rate',
+ rateWindow: updateInterval,
+ visibleDuration: 0.25,
+ forceCreate: true,
+ aggregate: true});
+ doDialog('tmplChartConfig.html', chart);
+ };
+
+ // The ui-popover dynamic html
+ $scope.aggregateTip = '';
+ // disable popover tips for non-integer cells
+ $scope.aggregateTipEnabled = function (row) {
+ let tip = row.entity.aggregateTip;
+ return (tip && tip.length) ? 'true' : 'false';
+ };
+ // convert the aggregate data into a table for the popover tip
+ $scope.genAggregateTip = function (row) {
+ let tip = row.entity.aggregateTip;
+ if (tip && tip.length) {
+ let data = angular.fromJson(tip);
+ let table = '<table class=\'tiptable\'><tbody>';
+ data.forEach (function (row) {
+ table += '<tr>';
+ table += '<td>' + row.node + '</td><td align=\'right\'>' + QDRService.utilities.pretty(row.val) + '</td>';
+ table += '</tr>';
+ });
+ table += '</tbody></table>';
+ $scope.aggregateTip = $sce.trustAsHtml(table);
+ }
+ };
+ var aggregateColumn = function () {
+ if ((aggregateEntities.indexOf($scope.selectedEntity) > -1 && $scope.detailCols.length != 3) ||
+ (aggregateEntities.indexOf($scope.selectedEntity) == -1 && $scope.detailCols.length != 2)) {
+ // column defs have to be reassigned and not spliced, so no push/pop
+ $scope.detailCols = [
+ {
+ field: 'attributeName',
+ displayName: 'Attribute',
+ cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName ui-grid-cell-contents">{{COL_FIELD CUSTOM_FILTERS | pretty}}<button ng-if="row.entity.graph" title="Click to view/add a graph" ng-click="grid.appScope.addToGraph(row.entity)" ng-class="{\'btn-success\': row.entity.chartExists}" class="btn"><i ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></button></div>'
+ },
+ {
+ field: 'attributeValue',
+ displayName: 'Value',
+ cellTemplate: '<div class="ui-grid-cell-contents" ng-class="{\'changed\': row.entity.changed == 1}">{{COL_FIELD CUSTOM_FILTERS | pretty}}</div>'
+ }
+ ];
+ if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
+ $scope.detailCols.push(
+ {
+ width: '10%',
+ field: 'aggregateValue',
+ displayName: 'Aggregate',
+ cellTemplate: '<div popover-enable="{{grid.appScope.aggregateTipEnabled(row)}}" uib-popover-html="grid.appScope.aggregateTip" popover-append-to-body="true" ng-mouseover="grid.appScope.genAggregateTip(row)" popover-trigger="\'mouseenter\'" class="listAggrValue ui-grid-cell-contents" ng-class="{\'changed\': row.entity.changed == 1}">{{COL_FIELD CUSTOM_FILTERS}} <button title="Click to view/add a graph" ng-if="row.entity.graph" ng-click="grid.appScope.addAllToGraph(row.entity)" ng-class="{\'btn-success\': row.entity.aggchartExists}" class="btn"><i ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></button></div>',
+ cellClass: 'aggregate'
+ }
+ );
+ }
+ }
+ if ($scope.selectedRecordName === '')
+ $scope.detailCols = [];
+
+ $scope.details.columnDefs = $scope.detailCols;
+ if ($scope.gridApi)
+ $scope.gridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+ };
+
+ $scope.gridApi = undefined;
+ // the table on the right of the page contains a row for each field in the selected record in the table on the left
+ $scope.desiredTableHeight = 340;
+ $scope.detailCols = [];
+ $scope.details = {
+ data: 'detailFields',
+ columnDefs: [
+ {
+ field: 'attributeName',
+ displayName: 'Attribute',
+ cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName ui-grid-cell-contents">{{COL_FIELD CUSTOM_FILTERS | pretty}}<button ng-if="row.entity.graph" title="Click to view/add a graph" ng-click="grid.appScope.addToGraph(row.entity)" ng-class="{\'btn-success\': row.entity.chartExists}" class="btn"><i ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></button></div>'
+ },
+ {
+ field: 'attributeValue',
+ displayName: 'Value',
+ cellTemplate: '<div class="ui-grid-cell-contents" ng-class="{\'changed\': row.entity.changed == 1}">{{COL_FIELD CUSTOM_FILTERS | pretty}}</div>'
+ }
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ multiSelect: false,
+ jqueryUIDraggable: true,
+ onRegisterApi: function(gridApi) {
+ $scope.gridApi = gridApi;
+ }
+ };
+
+ $scope.$on('$destroy', function() {
+ clearTimeout(updateIntervalHandle);
+ $(window).off('resize', resizer);
+ });
+
+ function gotMethodResponse (entity, context) {
+ let statusCode = context.message.application_properties.statusCode, note;
+ if (statusCode < 200 || statusCode >= 300) {
+ note = 'Failed to ' + $filter('Pascalcase')($scope.currentMode.op) + ' ' + entity + ': ' + context.message.application_properties.statusDescription;
+ QDR.Core.notification('error', note);
+ QDR.log.error('Error ' + note);
+ } else {
+ note = entity + ' ' + $filter('Pascalcase')($scope.currentMode.op) + 'd';
+ QDR.Core.notification('success', note);
+ QDR.log.debug('Success ' + note);
+ $scope.selectMode($scope.modes[0]);
+ }
+ }
+ $scope.ok = function () {
+ let attributes = {};
+ $scope.detailFields.forEach( function (field) {
+ let value = field.rawValue;
+ if (field.input === 'input') {
+ if (field.type === 'text' || field.type === 'disabled')
+ value = field.attributeValue;
+ } else if (field.input === 'select') {
+ value = field.selected;
+ } else if (field.input === 'boolean') {
+ value = field.rawValue;
+ }
+ if (value === '')
+ value = undefined;
+
+ if ((value && value != field['default']) || field.required || (field.name === 'role')) {
+ if (field.name !== 'identity')
+ attributes[field.name] = value;
+ }
+ });
+ QDRService.management.connection.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op)
+ .then(function (response) {gotMethodResponse($scope.selectedEntity, response.context);});
+ };
+ $scope.remove = function () {
+ let identity = $scope.selectedTreeNode.data.details.identity.value;
+ let attributes = {type: $scope.selectedEntity, identity: identity};
+ QDRService.management.connection.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op)
+ .then(function (response) {gotMethodResponse($scope.selectedEntity, response.context);});
+ };
+
+ function doDialog(template, chart) {
+
+ $uibModal.open({
+ backdrop: true,
+ keyboard: true,
+ backdropClick: true,
+ templateUrl: QDR.templatePath + template,
+ controller: 'QDR.ChartDialogController',
+ resolve: {
+ chart: function() {
+ return chart;
+ },
+ updateTick: function () {
+ return function () {};
+ },
+ dashboard: function () {
+ return $scope;
+ },
+ adding: function () {
+ return true;
+ }
+ }
+ }).result.then(function() {
+ QDRChartService.unRegisterChart(chart);
+ });
+ }
+ var setCurrentNode = function () {
+ let currentNode;
+ $scope.nodes.some( function (node, i) {
+ if (node.name === $scope.selectedNode) {
+ currentNode = $scope.nodes[i];
+ return true;
+ }
+ });
+ if ($scope.currentNode !== currentNode)
+ $scope.currentNode = currentNode;
+ };
+
+ let treeReady = false;
+ let serviceReady = false;
+ $scope.largeNetwork = QDRService.management.topology.isLargeNetwork();
+ if ($scope.largeNetwork)
+ aggregateEntities = [];
+
+ // called after we know for sure the schema is fetched and the routers are all ready
+ QDRService.management.topology.addUpdatedAction('initList', function () {
+ QDRService.management.topology.stopUpdating();
+ QDRService.management.topology.delUpdatedAction('initList');
+
+ $scope.nodes = QDRService.management.topology.nodeList().sort(function (a, b) { return a.name.toLowerCase() > b.name.toLowerCase();});
+ // unable to get node list? Bail.
+ if ($scope.nodes.length == 0) {
+ $location.path('/' + QDR.pluginName + '/connect');
+ $location.search('org', 'list');
+ }
+ if (!angular.isDefined($scope.selectedNode)) {
+ //QDR.log.debug("selectedNode was " + $scope.selectedNode);
+ if ($scope.nodes.length > 0) {
+ $scope.selectedNode = $scope.nodes[0].name;
+ $scope.selectedNodeId = $scope.nodes[0].id;
+ //QDR.log.debug("forcing selectedNode to " + $scope.selectedNode);
+ }
+ }
+ setCurrentNode();
+ if ($scope.currentNode == undefined) {
+ if ($scope.nodes.length > 0) {
+ $scope.selectedNode = $scope.nodes[0].name;
+ $scope.selectedNodeId = $scope.nodes[0].id;
+ $scope.currentNode = $scope.nodes[0];
+ }
+ }
+ let sortedEntities = Object.keys(QDRService.management.schema().entityTypes).sort();
+ sortedEntities.forEach( function (entity) {
+ if (excludedEntities.indexOf(entity) == -1) {
+ if (!angular.isDefined($scope.selectedEntity))
+ $scope.selectedEntity = entity;
+ $scope.operations = lookupOperations();
+ let e = new QDR.Folder(entity);
+ e.typeName = 'entity';
+ e.key = entity;
+ e.expanded = (expandedList.indexOf(entity) > -1);
+ let placeHolder = new QDR.Leaf('loading...');
+ placeHolder.addClass = 'loading';
+ e.children = [placeHolder];
+ entityTreeChildren.push(e);
+ }
+ });
+ serviceReady = true;
+ initTree();
+ });
+ // called by ng-init="treeReady()" in tmplListTree.html
+ $scope.treeReady = function () {
+ treeReady = true;
+ initTree();
+ };
+
+ // this gets called once tree is initialized
+ var onTreeInitialized = function () {
+ updateExpandedEntities();
+ };
+
+ var initTree = function () {
+ if (!treeReady || !serviceReady)
+ return;
+ $('#entityTree').fancytree({
+ activate: onTreeNodeActivated,
+ expand: onTreeNodeExpanded,
+ beforeActivate: onTreeNodeBeforeActivate,
+ beforeSelect: function(event, data){
+ // A node is about to be selected: prevent this for folders:
+ if( data.node.isFolder() ){
+ return false;
+ }
+ },
+ init: onTreeInitialized,
+ selectMode: 1,
+ autoCollapse: $scope.largeNetwork,
+ activeVisible: !$scope.largeNetwork,
+ clickFolderMode: 3,
+ debugLevel: 0,
+ extraClasses: {
+ expander: 'fa-angle',
+ connector: 'fancytree-no-connector'
+ },
+ source: entityTreeChildren
+ });
+ };
+ QDRService.management.topology.ensureAllEntities({entity: 'connection'}, function () {
+ QDRService.management.topology.setUpdateEntities(['connection']);
+ // keep the list of routers up to date
+ QDRService.management.topology.startUpdating(true);
+ });
+
+ updateIntervalHandle = setInterval(function () {
+ if (!treeReady || !serviceReady)
+ return;
+
+ let now = Date.now();
+ if (((now - last_updated) >= updateInterval) || updateNow) {
+ updateNow = false;
+ $timeout( function () {
+ updateExpandedEntities();
+ resizer();
+ });
+ }
+ }, 100);
+
+ }]);
+
+ return QDR;
+
+} (QDR || {}));
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrListChart.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrListChart.js b/console/stand-alone/plugin/js/qdrListChart.js
new file mode 100644
index 0000000..711046e
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrListChart.js
@@ -0,0 +1,146 @@
+/*
+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.
+*/
+'use strict';
+/* global angular */
+
+/**
+ * @module QDR
+ */
+
+var QDR = (function(QDR) {
+
+ QDR.module.controller('QDR.ListChartController', function ($scope, $uibModalInstance, $uibModal, $location, QDRService, QDRChartService, chart, nodeName) {
+
+ $scope.chart = chart;
+ $scope.dialogSvgChart = null;
+ let updateTimer = null;
+ $scope.svgDivId = 'dialogChart'; // the div id for the svg chart
+
+ $scope.showChartsPage = function () {
+ cleanup();
+ $uibModalInstance.close(true);
+ $location.path(QDR.pluginRoot + '/charts');
+ };
+
+ $scope.addHChart = function () {
+ QDRChartService.addHDash($scope.chart);
+ cleanup();
+ $uibModalInstance.close(true);
+ };
+
+ $scope.addToDashboardLink = function () {
+ let href = '#/' + QDR.pluginName + '/charts';
+ let size = angular.toJson({
+ size_x: 2,
+ size_y: 2
+ });
+
+ let params = angular.toJson({chid: $scope.chart.id()});
+ let title = 'Dispatch - ' + nodeName;
+ return '/hawtio/#/dashboard/add?tab=dashboard' +
+ '&href=' + encodeURIComponent(href) +
+ '&routeParams=' + encodeURIComponent(params) +
+ '&title=' + encodeURIComponent(title) +
+ '&size=' + encodeURIComponent(size);
+ };
+
+
+ $scope.addChartsPage = function () {
+ QDRChartService.addDashboard($scope.chart);
+ };
+
+ $scope.delChartsPage = function () {
+ QDRChartService.delDashboard($scope.chart);
+ };
+
+ $scope.isOnChartsPage = function () {
+ return $scope.chart.dashboard;
+ };
+
+ var showChart = function () {
+ // we need a width and height before generating the chart
+ let div = angular.element('#pfDialogChart');
+ if (!div.width()) {
+ setTimeout(showChart, 100);
+ return;
+ }
+ $scope.pfDialogSvgChart = new QDRChartService.pfAreaChart($scope.chart, 'pfDialogChart');
+ updateDialogChart();
+ };
+ showChart();
+
+ var updateDialogChart = function () {
+ if ($scope.pfDialogSvgChart) {
+ $scope.pfDialogSvgChart.tick();
+ }
+ if (updateTimer)
+ clearTimeout(updateTimer);
+ updateTimer = setTimeout(updateDialogChart, 1000);
+ };
+
+ var cleanup = function () {
+ if (updateTimer) {
+ clearTimeout(updateTimer);
+ updateTimer = null;
+ }
+ if (!$scope.chart.hdash && !$scope.chart.dashboard)
+ QDRChartService.unRegisterChart($scope.chart); // remove the chart
+
+ };
+ $scope.ok = function () {
+ cleanup();
+ $uibModalInstance.close(true);
+ };
+
+ $scope.editChart = function () {
+ doDialog('tmplChartConfig.html', chart);
+ };
+
+ function doDialog(template, chart) {
+
+ $uibModal.open({
+ backdrop: true,
+ keyboard: true,
+ backdropClick: true,
+ templateUrl: QDR.templatePath + template,
+ controller: 'QDR.ChartDialogController',
+ resolve: {
+ chart: function() {
+ return chart;
+ },
+ updateTick: function () {
+ return function () {};
+ },
+ dashboard: function () {
+ return $scope;
+ },
+ adding: function () {
+ return true;
+ }
+ }
+ }).result.then(function() {
+ $scope.ok();
+ });
+ }
+
+ });
+
+ return QDR;
+
+} (QDR || {}));
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[6/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js
files that were in the npm repository
Posted by ea...@apache.org.
DISPATCH-1000 Restore the html/css/js files that were in the npm repository
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/dfd3cf7c
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/dfd3cf7c
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/dfd3cf7c
Branch: refs/heads/master
Commit: dfd3cf7cb982fe8ffe9296a0100c3341660fb2b7
Parents: 94e55a8
Author: Ernest Allen <ea...@redhat.com>
Authored: Thu May 17 12:09:07 2018 -0400
Committer: Ernest Allen <ea...@redhat.com>
Committed: Thu May 17 12:09:07 2018 -0400
----------------------------------------------------------------------
.gitignore | 1 +
console/stand-alone/index.html | 34 +-
console/stand-alone/plugin/css/brokers.ttf | Bin 0 -> 2272 bytes
console/stand-alone/plugin/css/dispatch.css | 706 ++++++
console/stand-alone/plugin/css/dispatchpf.css | 473 ++++
console/stand-alone/plugin/css/plugin.css | 1019 +++++++++
console/stand-alone/plugin/html/qdrCharts.html | 48 +
console/stand-alone/plugin/html/qdrConnect.html | 87 +
console/stand-alone/plugin/html/qdrList.html | 144 ++
.../stand-alone/plugin/html/qdrOverview.html | 283 +++
console/stand-alone/plugin/html/qdrSchema.html | 62 +
.../stand-alone/plugin/html/qdrTopology.html | 146 ++
.../plugin/html/tmplChartConfig.html | 85 +
.../stand-alone/plugin/html/tmplListChart.html | 40 +
.../stand-alone/plugin/html/tmplListTree.html | 42 +
.../plugin/html/tmplOverviewTree.html | 48 +
.../stand-alone/plugin/js/dlgChartController.js | 204 ++
.../stand-alone/plugin/js/qdrChartService.js | 859 +++++++
console/stand-alone/plugin/js/qdrCharts.js | 160 ++
console/stand-alone/plugin/js/qdrGlobals.js | 46 +
console/stand-alone/plugin/js/qdrList.js | 938 ++++++++
console/stand-alone/plugin/js/qdrListChart.js | 146 ++
console/stand-alone/plugin/js/qdrOverview.js | 1774 +++++++++++++++
.../plugin/js/qdrOverviewChartsController.js | 134 ++
.../plugin/js/qdrOverviewLogsController.js | 70 +
console/stand-alone/plugin/js/qdrSchema.js | 83 +
console/stand-alone/plugin/js/qdrService.js | 269 +++
console/stand-alone/plugin/js/qdrSettings.js | 172 ++
.../plugin/js/qdrTopAddressesController.js | 218 ++
console/stand-alone/plugin/js/qdrTopology.js | 2098 ++++++++++++++++++
30 files changed, 10372 insertions(+), 17 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index b4164bb..4ab831e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ tests/policy-1/policy-*.json
.settings
console/test/topolgies/config-*
.history
+.vscode
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/index.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/index.html b/console/stand-alone/index.html
index 95c8c28..9602b42 100644
--- a/console/stand-alone/index.html
+++ b/console/stand-alone/index.html
@@ -44,9 +44,9 @@ under the License.
<link rel="stylesheet" href="node_modules/angular-ui-grid/ui-grid.css" type="text/css"/>
<!-- local Styles -->
- <link rel="stylesheet" href="node_modules/dispatch-console-pages/dist/css/plugin.css" type="text/css"/>
- <link rel="stylesheet" href="node_modules/dispatch-console-pages/dist/css/dispatch.css" type="text/css"/>
- <link rel="stylesheet" href="node_modules/dispatch-console-pages/dist/css/dispatchpf.css" type="text/css"/>
+ <link rel="stylesheet" href="plugin/css/plugin.css" type="text/css"/>
+ <link rel="stylesheet" href="plugin/css/dispatch.css" type="text/css"/>
+ <link rel="stylesheet" href="plugin/css/dispatchpf.css" type="text/css"/>
</head>
<body ng-app="QDR" ng-controller="QDR.Core">
@@ -133,20 +133,20 @@ under the License.
<script type="text/javascript" src="node_modules/dispatch-management/dist/dispatch-management.js"></script>
<script type="text/javascript" src="plugin/js/dispatchPlugin.js"></script>
<script type="text/javascript" src="plugin/js/navbar.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrGlobals.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrOverview.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrOverviewLogsController.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrOverviewChartsController.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrTopAddressesController.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/dlgChartController.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrList.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrListChart.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrCharts.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrSchema.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrService.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrChartService.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrTopology.js"></script>
-<script type="text/javascript" src="node_modules/dispatch-console-pages/dist/js/qdrSettings.js"></script>
+<script type="text/javascript" src="plugin/js/qdrGlobals.js"></script>
+<script type="text/javascript" src="plugin/js/qdrOverview.js"></script>
+<script type="text/javascript" src="plugin/js/qdrOverviewLogsController.js"></script>
+<script type="text/javascript" src="plugin/js/qdrOverviewChartsController.js"></script>
+<script type="text/javascript" src="plugin/js/qdrTopAddressesController.js"></script>
+<script type="text/javascript" src="plugin/js/dlgChartController.js"></script>
+<script type="text/javascript" src="plugin/js/qdrList.js"></script>
+<script type="text/javascript" src="plugin/js/qdrListChart.js"></script>
+<script type="text/javascript" src="plugin/js/qdrCharts.js"></script>
+<script type="text/javascript" src="plugin/js/qdrSchema.js"></script>
+<script type="text/javascript" src="plugin/js/qdrService.js"></script>
+<script type="text/javascript" src="plugin/js/qdrChartService.js"></script>
+<script type="text/javascript" src="plugin/js/qdrTopology.js"></script>
+<script type="text/javascript" src="plugin/js/qdrSettings.js"></script>
<script type="text/javascript" src="plugin/js/chord/ribbon/ribbon.js"></script>
<script type="text/javascript" src="plugin/js/chord/matrix.js"></script>
<script type="text/javascript" src="plugin/js/chord/filters.js"></script>
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/css/brokers.ttf
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/brokers.ttf b/console/stand-alone/plugin/css/brokers.ttf
new file mode 100644
index 0000000..ae83968
Binary files /dev/null and b/console/stand-alone/plugin/css/brokers.ttf differ
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/css/dispatch.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/dispatch.css b/console/stand-alone/plugin/css/dispatch.css
new file mode 100644
index 0000000..0096f95
--- /dev/null
+++ b/console/stand-alone/plugin/css/dispatch.css
@@ -0,0 +1,706 @@
+/*
+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.
+*/
+
+svg {
+ background-color: transparent;
+ cursor: default;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+svg:not(.active):not(.ctrl) {
+ cursor: crosshair;
+}
+#end-arrow-selected, #start-arrow-selected {
+ stroke: #33F;
+ fill: #33F;
+}
+path.link.selected {
+ /* stroke-dasharray: 10,2; */
+ stroke: #33F !important;
+}
+
+path.link {
+ fill: #000;
+ stroke: #000;
+ stroke-width: 4px;
+ cursor: default;
+}
+
+svg:not(.active):not(.ctrl) path.link {
+ cursor: pointer;
+}
+
+path.link.small {
+ stroke-width: 2.5;
+ stroke: darkgray;
+}
+path.link.highlighted {
+ stroke: #6F6 !important;
+}
+marker#start-arrow-highlighted,
+marker#end-arrow-highlighted {
+ fill: #6F6;
+}
+marker#start-arrow-small,
+marker#end-arrow-small {
+ fill: darkgray;
+}
+
+path.link.dragline {
+ pointer-events: none;
+}
+
+path.link.hidden {
+ stroke-width: 0;
+}
+
+circle.node {
+ stroke-width: 1.5px;
+ cursor: pointer;
+ stroke: darkgray;
+}
+
+circle.node.reflexive {
+ stroke: #F00 !important;
+ stroke-width: 2.5px;
+}
+circle.node.selected {
+ stroke: #6F6 !important;
+ stroke-width: 2px;
+ fill: #e0e0ff !important;
+}
+circle.node.highlighted {
+ stroke: #6F6;
+}
+circle.node.inter-router {
+ fill: #EAEAEA;
+}
+circle.node.normal.in {
+ fill: #F0F000;
+}
+circle.node.normal.out {
+ fill: #C0F0C0;
+}
+circle.node.on-demand {
+ fill: #C0FFC0;
+}
+circle.node.on-demand.artemis {
+ fill: #FCC;
+ /*opacity: 0.2; */
+}
+
+circle.node.fixed {
+ stroke-dasharray: 10,2;
+}
+text {
+ font: 12px sans-serif;
+ pointer-events: none;
+ /*font-family: monospace;*/
+
+}
+
+.tooltipsy
+{
+ padding: 10px;
+/* max-width: 320px;*/
+ color: #303030;
+ background-color: #fcfcfe;
+ border: 1px solid #deca7e;
+ border-radius: 5px;
+}
+
+.tiptable {
+
+}
+.tiptable tr {
+ border-bottom: 1px solid #ccc;
+}
+
+.tiptable tr:last-child {
+ border-bottom: 0px;
+}
+
+.tiptable tr:nth-child(even) {
+ background: #fcfcfe;
+}
+.tiptable tr:nth-child(odd) {
+ background: #FFF
+}
+
+text.id {
+ text-anchor: middle;
+ font-weight: bold;
+}
+
+text.label {
+ text-anchor: start;
+ font-weight: bold;
+}
+
+.row-fluid.tertiary {
+ position: relative;
+ left: 20px;
+}
+
+.row-fluid.tertiary.left {
+ float: left;
+}
+
+.row-fluid.tertiary.panel {
+ width: 410px;
+ /*height: 100%; */
+}
+
+div#topologyForm .ngViewport, div#topologyForm .gridStyle {
+ height: auto !important;
+ min-height: initial !important;
+ overflow: initial;
+}
+
+div#multiple_details, div#link_details {
+ height: 300px;
+ width: 700px;
+ display: none;
+ padding: 0.5em;
+ border: 1px solid;
+ position: absolute;
+ background-color: white;
+ max-height: 330px !important;
+ overflow: hidden;
+}
+div#multiple_details div.ngRow.selected {
+ background-color: #c9dde1 !important;
+}
+
+div.grid-values {
+ text-align: right;
+}
+
+div.grid-values.ngCellText span {
+ padding-right: 4px;
+}
+
+.panel-adjacent {
+ margin-left: 430px;
+}
+
+/* globe */
+.land {
+ fill: #999;
+ stroke-opacity: 1;
+}
+
+.graticule {
+ fill: none;
+ stroke: black;
+ stroke-width:.5;
+ opacity:.1;
+}
+
+.labels {
+ font: 18px sans-serif;
+ fill: black;
+ opacity: .85;
+ text-anchor: middle;
+}
+
+.noclicks { pointer-events:none; }
+
+.point { opacity:.6; }
+
+.arcs {
+ opacity:.7;
+ stroke: darkgreen;
+ stroke-width: 3;
+}
+.flyers {
+ stroke-width:1;
+ opacity: 0;
+ stroke: darkred;
+}
+.arc, .flyer {
+ stroke-linejoin: round;
+ fill:none;
+}
+.arc { }
+.arc:hover {
+ stroke: darkred;
+}
+.flyer { }
+.flyer:hover {
+ stroke: darkgreen;
+}
+.arc.inter-router {
+ stroke: darkblue;
+}
+
+#addNodeForm {
+ padding: 1em;
+}
+
+
+li.currentStep {
+ font-weight: bold;
+}
+
+.qdrTopology div.panel {
+ position: absolute;
+}
+/*
+.ui-dialog-titlebar {
+ border: 0;
+ background: transparent;
+}
+*/
+
+/*
+.ui-tabs.ui-tabs-vertical {
+ padding: 0;
+ width: 48em;
+}
+.ui-tabs.ui-tabs-vertical .ui-widget-header {
+ border: none;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav {
+ float: left;
+ width: 10em;
+ background: #CCC;
+ border-radius: 4px 0 0 4px;
+ border-right: 1px solid gray;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li {
+ clear: left;
+ width: 100%;
+ margin: 0.1em 0;
+ border: 1px solid gray;
+ border-width: 1px 0 1px 1px;
+ border-radius: 4px 0 0 4px;
+ overflow: hidden;
+ position: relative;
+ right: -2px;
+ z-index: 2;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li a {
+ display: block;
+ width: 100%;
+ padding: 0.1em 1em;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li a:hover {
+ cursor: pointer;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li.ui-tabs-active {
+ margin-bottom: 0.2em;
+ padding-bottom: 0;
+ border-right: 1px solid white;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li:last-child {
+ margin-bottom: 10px;
+}
+.ui-tabs.ui-tabs-vertical .ui-tabs-panel {
+ float: left;
+ width: 34em;
+ border-left: 1px solid gray;
+ border-radius: 0;
+ position: relative;
+ left: -1px;
+}
+
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected {
+ right: -3px !important;
+}
+
+.ui-tabs li i.ui-icon {
+ display: inline-block;
+}
+*/
+.ui-tabs .ui-tabs-panel {
+ /* padding-top: 0 !important; */
+}
+
+.ui-widget-content fieldset {
+ float: left;
+ padding: 0 1em 0 0;
+}
+
+.entity-description {
+ color: #960;
+ font-size: 90%;
+}
+
+.attr-description {
+ padding-top: 1.5em;
+ float: right;
+ width: 17em;
+}
+.attr-annotations {
+ padding-top: 2.5em;
+ clear: both;
+}
+.attr-annotations > span {
+ padding-top: 0.5em;
+ border-top: 1px dashed darkgray;
+ display: block;
+}
+
+.attr-type {
+ color: #990;
+ font-size: 85%;
+}
+.attr-required {
+ color: red;
+ font-size: 85%;
+}
+.attr-unique {
+ color: green;
+ font-size: 85%;
+}
+
+#tabs.nodeEntities {
+ border: 0;
+}
+
+#tabs ul.nodeTabs {
+ background: #fff;
+}
+
+#tabs #Container {
+ border-left: 1px solid #aaa;
+}
+
+#tabs.ui-tabs .ui-tabs-nav li {
+ border-bottom: 1px solid #aaa !important;
+}
+
+.entity-fields {
+ /* height: 400px; */
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+
+div.boolean label:first-child {
+ float: left;
+ margin-right: 1em;
+}
+div.boolean {
+ padding-bottom: 1em;
+}
+
+.entity-fields label {
+ font-weight: 600;
+ margin-top: 0.5em;
+ display: inline;
+}
+
+.aggregate {
+ text-align: right;
+}
+
+.aggregate i {
+ float: right;
+ /* margin: 3px 3px 3px 8px; */
+}
+
+.aggregate .hastip {
+ padding: 5px;
+}
+
+.subTip .tipsy-inner {
+ background-color: white;
+ color: black;
+ font-size: 1.3em;
+ border: 1px solid black;
+}
+
+.subTip .tipsy-arrow-n { border-bottom-color: black; }
+.subTip .tipsy-arrow-s { border-top-color: black; }
+.subTip .tipsy-arrow-e { border-left-color: black; }
+.subTip .tipsy-arrow-w { border-right-color: black; }
+
+
+.contextMenu {
+ display:none;
+ position:absolute;
+ left:30px;
+ top:-30px;
+ z-index:999;
+ /* width:300px; */
+}
+.contextMenu ul {
+ width:300px;
+ margin:0;
+ padding-left:0;
+ list-style:none;
+ background:#fff;
+ color:#333;
+ font-weight: 600;
+ /* -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; */
+ -moz-box-shadow:5px 5px 5px #ddd; -webkit-box-shadow:5px 5px 5px #999; box-shadow:5px 5px 5px #ddd;
+ border: 1px solid #aaa;
+}
+.contextMenu ul li {
+ padding:5px 10px;
+ /* border-bottom: solid 1px #ccc; */
+}
+.contextMenu ul li:hover {
+ background:#4a90d9; color:#fff;
+}
+.contextMenu ul li:last-child {
+ border:none;
+}
+
+.na {
+ display: none;
+}
+.contextMenu ul li.new {
+ display: block;
+}
+.contextMenu ul li.adding, .contextMenu ul li.adding + li {
+ display: block;
+}
+.contextMenu ul li.force-display {
+ display: block;
+}
+.contextMenu ul li.context-separator {
+ background-color: lightgray;
+ height: 1px;
+ padding: 0;
+}
+
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li.separated {
+ margin-top: 1em;
+}
+
+#crosssection {
+ display: none;
+ position: absolute;
+ top: 200px;
+ left: 600px;
+}
+
+.cross-rect {
+ /* fill: #cfe2f3; */
+}
+.cross-line {
+ stroke: black;
+ stroke-width: 4px;
+}
+
+.node circle {
+/* fill: rgb(31, 119, 180);
+ fill-opacity: .25; */
+ fill: #cfe2f3;
+ fill-opacity: .98;
+ stroke: black;
+ stroke-width: 3px;
+}
+
+circle.subcircle {
+ stroke-width: 1px;
+ /* stroke-dasharray: 2; */
+ fill-opacity: 0;
+ stroke: darkgray;
+}
+
+.leaf circle {
+ fill: #6fa8dc;
+ fill-opacity: 0.95;
+ stroke-width: 3px;
+}
+
+.leaf circle[title] {
+ font-family: monospace;
+
+}
+
+#svg_legend {
+ position: absolute;
+ top: 110px;
+ right: 0;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ background-color: #fcfcfc;
+ margin-right: 1.3em;
+ padding: 1em;
+}
+
+#svg_legend svg {
+ height: 235px;
+ width: 180px;
+}
+
+#multiple_details div.gridStyle {
+/* height: 50em; */
+ min-height: 70px !important;
+ height: auto !important;
+}
+
+#multiple_details .ngViewport {
+ height: auto !important;
+}
+
+#multiple_details .gridCellButton button, #link_details .gridCellButton button {
+ margin: .25em .4em;
+ font-size: 12px;
+ height: 2em;
+ padding-top: .1em;
+}
+
+#linkFilter {
+ display: none;
+ padding: 0.5em;
+ border: 1px solid grey;
+ background-color: #F0F0F0;
+ position: absolute;
+ z-index: 100;
+ right: 1em;
+}
+div.formLine label, div.formLine input {
+ display: inline-block;
+ padding: 0 8px;
+}
+
+span.filter-icon {
+ padding-left: 1em;
+}
+
+button.filter-close {
+ width: 15px;
+ height: 20px;
+ padding: 0;
+ position: absolute;
+ right: 4px;
+ top: 4px;
+}
+
+div.filter-title h6 {
+ margin: 0 0 0.5em 0;
+}
+
+.links button.btn-filter {
+ padding: 0 1em 0 0;
+ margin-left: 1em;
+ font-size: 1em;
+}
+
+button.btn-filter {
+ float: right;
+}
+span.fancytree-expanded button.btn-filter,
+a.fancytree-title:hover button.btn-filter {
+ visibility: visible;
+}
+
+div.hdash-button a {
+ color: white;
+}
+
+.linkDirIn {
+ color: red;
+ background-color: #f3f3f3;
+}
+
+.linkDirOut {
+ color: blue;
+ background-color: white;
+}
+
+div.topoGrid .ui-grid-viewport {
+ overflow: hidden !important;
+}
+
+@-moz-document url-prefix() {
+ .btn {padding: 2px 12px 8px !important;}
+ #overview-controller .btn {padding: 4px 12px !important;}
+ #overview-controller .btn.filter-close {padding: 0 !important;}
+}
+
+.ui-fancytree.fancytree-container {
+ font-size: 14px;
+}
+
+.grid-title {
+ background-color: #FAFAFA;
+ background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+ background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+ background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFF', endColorstr='#F2F2F2', GradientType=0);
+ border-bottom: 1px solid #d4d4d4;
+ text-shadow: 0 1px 0 #FFFFFF;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ margin: 0 0 10px 0;
+ padding-bottom: 4px;
+}
+
+.expand-collapse {
+ float: right;
+ margin-right: 0.5em;
+}
+
+.pane-viewport {
+ top: 26px !important;
+}
+.fancytree-node.loading {
+ position: initial;
+}
+
+.newChart {
+ float: right;
+}
+
+select.unique, input.unique {
+ border: 2px solid blue;
+}
+select.required, input.required {
+ border: 2px solid black;
+}
+
+.required-indicator {
+ padding-left: 0.5em;
+ font-size: 0.85em;
+ vertical-align: super;
+}
+
+.required-indicator::before {
+ content: '(required)'
+}
+
+.unique-indicator {
+ padding-left: 0.5em;
+ font-size: 0.85em;
+ vertical-align: super;
+}
+
+.unique-indicator::before {
+ content: '(must be unique if supplied)'
+}
+
+.unique-indicator
+
+.error {
+ color: red;
+ font-weight: bold;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/css/dispatchpf.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/dispatchpf.css b/console/stand-alone/plugin/css/dispatchpf.css
new file mode 100644
index 0000000..49e7d4b
--- /dev/null
+++ b/console/stand-alone/plugin/css/dispatchpf.css
@@ -0,0 +1,473 @@
+/*
+ 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
+*/
+
+/* The following are for matching Patternfly styles */
+span.logo {
+ letter-spacing: 3px;
+}
+
+.navbar-pf .navbar-brand {
+ padding: 2px 0 2px;
+}
+/* using fancytree instead of treeview */
+
+#overtree ul.fancytree-container,
+#entityTree ul.fancytree-container,
+#schema ul.fancytree-container {
+ background-color: transparent;
+ border: 0px;
+}
+
+span.fancytree-folder a {
+ font-weight: normal;
+ font-size: 12px;
+}
+
+ul.fancytree-container li,
+span.fancytree-icon {
+ background-image: none;
+}
+
+.fancytree-exp-c span.fa-angle, .fancytree-exp-e span.fa-angle,
+.fancytree-exp-cl span.fa-angle, .fancytree-exp-el span.fa-angle {
+ margin-right: 5px;
+}
+.fancytree-exp-c span.fa-angle:before, .fancytree-exp-cl span.fa-angle:before {
+ font-family: FontAwesome;
+ content: "\f105";
+}
+.fancytree-exp-e .fancytree-expander:before, .fancytree-exp-el .fancytree-expander:before {
+ font-family: FontAwesome;
+ content: "\f107";
+}
+.fancytree-exp-c .fancytree-expander:before, .fancytree-exp-cl .fancytree-expander:before {
+ font-family: FontAwesome;
+ content: "\f105";
+}
+.fancytree-exp-e span.fa-angle:before, .fancytree-exp-el span.fa-angle:before {
+ font-family: FontAwesome;
+ content: "\f107";
+}
+
+.fancytree-ico-ef span.fancytree-icon, .fancytree-ico-cf span.fancytree-icon {
+ background-image: initial;
+ font-family: FontAwesome;
+}
+
+/* closed folder */
+.fancytree-ico-cf span.fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f07b";
+}
+/* expanded folder */
+.fancytree-ico-ef span.fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f07c";
+}
+/* chart */
+.fancytree-ico-cf.charts span.fancytree-icon:before, .fancytree-ico-ef.charts span.fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f080";
+}
+
+
+ul.fancytree-container a {
+ color: #363636;
+}
+
+/* override left and right pane styles */
+#schema .col-sm-3,
+.qdr-topology-form.col-sm-3 {
+ padding-left: 0px;
+}
+
+.qdr-topology-form.col-sm-3 {
+ padding-right: 0px;
+}
+
+#overview-controller .ngViewport {
+ height: auto !important;
+}
+
+.pane-wrapper {
+ padding-top: 0.5em;
+}
+
+.pane-viewport {
+ position: relative;
+ /* height: calc(100vh - 20px); */
+ overflow: auto;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline;
+ width: auto;
+ height: auto;
+ line-height: normal;
+ vertical-align: baseline;
+ background-image: none;
+ background-position: 0% 0%;
+ background-repeat: repeat;
+ margin-top: 0;
+}
+[class^="icon-"],
+[class*=" icon-"] {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ text-decoration: inherit;
+ -webkit-font-smoothing: antialiased;
+ *margin-right: .3em;
+}
+
+[class^="icon-"]:before,
+[class*=" icon-"]:before {
+ text-decoration: inherit;
+ display: inline-block;
+ speak: none;
+}
+
+.icon-bar-chart:before {
+ font-family: FontAwesome;
+ content: "\f080";
+}
+
+.icon-step-forward:before {
+ font-family: FontAwesome;
+ content: "\f051";
+}
+.icon-step-backward:before {
+ font-family: FontAwesome;
+ content: "\f048";
+}
+
+.icon-close:before {
+ font-family: FontAwesome;
+ content: "\f410";
+}
+
+[class^="icon-"]:before, [class*=" icon-"]:before {
+ text-decoration: inherit;
+ display: inline-block;
+ speak: none;
+}
+
+#svg_legend {
+ top: 10px;
+}
+
+#topologyForm > div {
+ width: auto;
+}
+
+div.chartContainer {
+ width: auto;
+ margin-top: 1em;
+}
+
+#dispatch-login-container {
+ width: 90em;
+}
+
+#dispatch-login-container .alert {
+ padding-right: 47px;
+}
+
+.fa-edit:before, .fa-trash-o:before {
+ font-family: FontAwesome;
+}
+
+div.qdr-topology.pane.left {
+ width: 400px;
+ top: 0;
+}
+/*
+.treeview .list-group-item.node-selected {
+ color: #FFFFFF !important;
+ background-color: #428bca !important;
+}
+*/
+span.fancytree-expander {
+ display: none;
+}
+
+#overview-controller span.fancytree-expander {
+ display: inline-block;
+}
+
+.fancytree-active .fancytree-title,
+.fancytree-active .fancytree-icon,
+.fancytree-active .fancytree-expander {
+ color: white;
+}
+
+.ui-grid {
+ border: 0px;
+}
+
+.ui-grid-header {
+ border: 1px solid #d4d4d4;
+}
+
+#overview-controller .sidebar-pf-left, #list-controller .sidebar-pf-left {
+ padding-right: 0px;
+}
+
+#crosshtml {
+ position: absolute;
+ display: none;
+ padding: 1em;
+ border: 1px solid grey;
+ background-color: white;
+ border-radius: 4px;
+}
+
+.pane-bar {
+ float: right;
+ height: calc(100vh - 75px);
+ width: 6px;
+ background-color: white;
+ border: 1px solid #e0e0e0;
+ position: absolute;
+ top: 0;
+ right: -6px;
+ cursor: ew-resize;
+
+
+ display: none;
+}
+
+.dispatch-c3-tooltip {
+ border-collapse: collapse;
+ border-spacing: 0;
+ background-color: #fff;
+ empty-cells: show;
+ -webkit-box-shadow: 7px 7px 12px -9px #777777;
+ -moz-box-shadow: 7px 7px 12px -9px #777777;
+ box-shadow: 7px 7px 12px -9px #777777;
+ opacity: 0.9;
+
+}
+
+.dispatch-c3-tooltip tr {
+ border: 1px solid #CCC;
+}
+
+.dispatch-c3-tooltip th {
+ background-color: #aaa;
+ font-size: 13px;
+ padding: 2px 5px;
+ text-align: left;
+ color: #FFF;
+ font-weight: normal;
+}
+
+.dispatch-c3-tooltip td {
+ font-size: 13px;
+ padding: 3px 6px;
+ background-color: #fff;
+ border-left: 1px dotted #999;
+}
+
+.chart-tip-legend {
+ width: 10px;
+ height: 10px;
+ display: inline-block;
+ margin-right: 4px;
+}
+
+th.text-center {
+ text-align: center;
+}
+
+.clearfix:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+#topAddresses.grid {
+ min-height: 190px;
+}
+
+circle.c3-circle {
+ display: none;
+}
+
+circle.c3-circle._expanded_ {
+ display: inline;
+}
+
+#main.container-fluid {
+ position: relative;
+}
+
+.chord-diagram:before {
+ font-style: normal;
+ font-family: FontAwesome;
+ content: "\f021";
+}
+
+.code-branch:before {
+ font-style: normal;
+ font-family: FontAwesome;
+ /* content: "\f126"; */
+ content: "\f085"; /* cogs */
+}
+
+.icon-list:before {
+ font-style: normal;
+ font-family: FontAwesome;
+ content: "\f0db";
+}
+
+.icon-power:before {
+ font-style: normal;
+ font-family: FontAwesome;
+ content: "\f011";
+}
+
+.icon-ellipsis-v:before {
+ font-style: normal;
+ font-family: FontAwesome;
+ content: "\f142";
+}
+
+.icon-schema:before {
+ font-style: normal;
+ font-family: FontAwesome;
+ content: "\f0ca";
+}
+
+/* convert fancytree theme to dark */
+span.fancytree-title {
+ color: white;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ width: 16em;
+}
+
+span.fancytree-node:hover {
+ color: black;
+ background-color: white;
+}
+span.fancytree-node:hover span.fancytree-title {
+ color: black;
+}
+
+span.fancytree-node.fancytree-active:hover,
+span.fancytree-node.fancytree-active:hover span.fancytree-title {
+ color: white;
+}
+
+span.fancytree-expanded.fancytree-has-children, span.fancytree-expanded.fancytree-has-children + ul {
+ background-color: #444444;
+}
+span.fancytree-expanded.fancytree-has-children:hover {
+ background-color: white;
+}
+
+span.fancytree-node.fancytree-active {
+ background-color: #6aa3d5;
+}
+
+.navbar-fixed-top + .container-fluid {
+ padding-top: 71px;
+}
+
+@media (max-width: 768px) {
+ .navbar-fixed-top + .container-fluid {
+ padding-top: 38px;
+ }
+}
+
+.navbar-pf .navbar-nav>li>a {
+ padding: 14px 16px;
+}
+
+@media (min-width: 768px) {
+ .navbar-collapse.page-menu {
+ padding-right: 0;
+ /* height: calc(100vh - 74px) !important; */
+ }
+}
+@media (max-width: 768px) {
+ .navbar-collapse.page-menu {
+ position: absolute;
+ z-index: 1;
+ height: auto;
+ left: 0;
+ }
+}
+
+.page-menu {
+ padding-left: 0;
+ background: #333333;
+ z-index: 1;
+ transition: all .2s cubic-bezier(.35,0,.25,1);
+ position: absolute;
+ left: 0;
+ color: white;
+ width: 300px;
+}
+
+.page-menu-button {
+ /*color: black;
+ background: white; */
+ top: 0;
+ z-index: 2;
+ width: 1em;
+ padding: 4px 0 0 4px;
+ margin: 4px 0 0 4px;
+}
+.page-menu-button span.icon-bar {
+ display: block;
+ color: black;
+ background-color: black;
+ height: 4px;
+ width: 4px;
+ font-weight: bold;
+ border-radius: 2px;
+}
+
+.navbar-toggle.left {
+ float: left;
+}
+
+.hideLeft {
+ position: absolute;
+ right: -1.5em;
+ top: 0em;
+ border: 0;
+ color: white;
+ background-color: #444444;
+}
+
+.showLeft {
+ position: absolute;
+ top: 0em;
+ left: 0;
+ border: 0;
+ z-index: 3;
+ color: white;
+ background-color: #444444;
+}
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/css/plugin.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/plugin.css b/console/stand-alone/plugin/css/plugin.css
new file mode 100644
index 0000000..f7259dd
--- /dev/null
+++ b/console/stand-alone/plugin/css/plugin.css
@@ -0,0 +1,1019 @@
+/*
+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.
+*/
+main-display > .span8 {
+ height: 100%;
+ position: relative;
+}
+
+ul.qdrListNodes > li > span {
+ padding: 6px 20px; 6px; 6px;
+ display: block;
+}
+
+.qdrList .grid {
+ width: 20em;
+ margin-right: 0;
+ float: left;
+}
+
+
+.qdrList div.gridDetails {
+ width: auto;
+}
+
+.selectedItems {
+ /* margin-left: 21em; */
+}
+
+.qdrListPane {
+ top: 110px;
+}
+
+.qdrListActions {
+ width: auto;
+ margin-left: 1em;
+}
+
+div.listAttrName {
+ padding-top: 5px;
+}
+
+div.listAttrName button {
+ float: right;
+ margin: 3px 5px;
+}
+
+div.listAggrValue button {
+ margin: 3px 5px;
+}
+
+@-moz-document url-prefix() {
+ div.listAttrName button {
+ top: -24px;
+ position: relative;
+ margin: 3px 0px;
+ padding: 0 5px !important;
+ }
+}
+
+div.listAttrName i.icon-bar-chart.active, div.hastip i.icon-bar-chart.active, li.haschart i {
+ /*background-color: #AAFFAA; */
+}
+
+div#main div ul.nav li a:not(.btn) {
+ background: initial !important;
+}
+
+div#main div ul.nav li.active a {
+ background-color: #f0f0ff !important;
+}
+
+div#main.qdr {
+ margin-top: 56px !important;
+}
+
+div.charts-header {
+ font-size: 1.2em;
+ color: #666666;
+ margin: 1em 0;
+}
+
+.selectedNode, .selectedAction, .selectedEntity {
+ font-weight: 600;
+ color: #606066;
+}
+
+.okButton {
+ text-align: center;
+ margin: 1em;
+}
+
+span.showChartsLink {
+ border: 1px solid blue;
+ padding: 1px 2px;
+}
+
+div.listGraphs p {
+ margin: 1em 0 2em 2em;
+ text-align: center;
+}
+
+div.centered {
+ text-align: center;
+ margin: 4em;
+}
+
+.modal-body.centered {
+ margin: 0;
+}
+
+/* dialog */
+div.aChart {
+ height: 200px;
+ width: 400px;
+ margin: 1em;
+}
+
+/* dashboard */
+div.aChart.hDash {
+ /* width: 21em; */
+ /* height: 17em; */
+ width: 100%;
+ height: 87%;
+ margin: 0;
+ padding: 0;
+
+}
+div.chartContainer {
+ float: left;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+/* the x and y axis lines */
+.d3Chart g.axis path.domain {
+ stroke-width: 1;
+ stroke: black;
+}
+
+/* the line surrounding the area chart */
+div.d3Chart path {
+/* stroke: black; */
+/* stroke-width: 0; */
+/* opacity: 0.5; */
+}
+
+/* the line above the area chart */
+/* the color gets overridden */
+div.d3Chart path.line {
+ stroke: steelblue;
+ stroke-width: 1.5;
+ fill: none;
+ opacity: 1;
+}
+
+.mo-rect {
+ fill: #ffffdd;
+ stroke: #f0f0f0;
+ stroke-width: 1;
+}
+
+.mo-guide {
+ fill: none;
+ stroke: #d0d0d0;
+ stroke-width: 2;
+ stroke-dasharray: 3,3;
+}
+
+div.d3Chart .title {
+ text-decoration: underline;
+}
+
+
+.axis line, .axis path {
+ fill: none;
+ shape-rendering: crispEdges;
+ stroke-width: 1;
+ stroke: #000000;
+}
+
+.axis line {
+ stroke: #C0C0C0;
+ stroke-dasharray: 1,1;
+ opacity: 0.5;
+}
+
+.y.axis text, .x.axis text, .focus text, div.d3Chart .title {
+ font-size: 12px;
+}
+
+.y.axis path {
+ stroke: #000;
+ }
+
+.overlay {
+ fill: none;
+ pointer-events: all;
+ }
+
+.focus circle {
+ fill: none;
+ stroke: steelblue;
+ }
+.focus .fo-table {
+ /* box-shadow: 2px 2px 3px #EEE; */
+}
+
+div.d3Chart {
+ /* padding: 1em 0; */
+ /* border: 1px solid #C0C0C0; */
+ margin-bottom: 2em;
+}
+div.d3Chart.hDash {
+ border: 0px;
+}
+
+div.d3Chart .axis path {
+ display: inherit;
+}
+
+.c3-circle {
+ /* display: none; */
+}
+
+.fo-table {
+ border: 1px solid darkgray;
+ background-color: white;
+ font-size: .85em;
+}
+
+.fo-table td {
+ padding: 4px;
+ border-left: 1px solid darkgray;
+}
+.fo-table tr.detail td {
+ padding: 1px 4px;
+}
+.fo-title {
+ color: white;
+ background-color: darkgray;
+}
+
+.fo-table-legend {
+ width: 8px;
+ height: 8px;
+ border: 1px solid black;
+ margin: 0 4px;
+ display: inline-block;
+}
+
+svg .legend {
+ dominant-baseline: central;
+}
+
+div.chartContainer div.aChart {
+ margin-top: 0.5em;
+}
+
+div#main.qdr div ul.nav li.active a {
+ background-color: #e0e0ff !important;
+ color: #000000;
+}
+
+div#main.qdr .selected, .box.selected {
+ color: #000000;
+ text-shadow: none;
+}
+
+/* the selected node on the list page */
+div.qdrList li.active, ul.qdrListNodes li.active {
+ background-color: #e0e0ff;
+}
+
+div.qdr-attributes span.fancytree-selected a {
+ background-color: #e0e0ff;
+}
+div.qdr-attributes.pane, div.qdr-topology.pane {
+ position: absolute;
+ margin-left: 10px;
+}
+div.qdr-overview.pane {
+ position: absolute;
+}
+div.qdr-topology.pane.left {
+ width: auto;
+ /*border-right: 1px solid lightgray; */
+}
+
+/* the selected row in the name table */
+div#main.qdr div.qdrList div.selected {
+ background-color: #e0e0ff !important;
+}
+
+#dialogChart, #dialogEditChart {
+ height: 220px;
+}
+
+.chartOptions .modal-body {
+ overflow-y: initial;
+}
+
+div.qdrCharts p.chartLabels button {
+ float: right;
+}
+
+div.qdrCharts p.chartLabels {
+ padding-right: 1em;;
+ }
+
+p.dialogHeader {
+ text-align: center;
+}
+
+p.dialogHeader input {
+ margin-top: 10px;
+ width: 480px;
+}
+
+.ui-slider-tick {
+ position: absolute;
+ background-color: #666;
+ width: 2px;
+ height: 8px;
+ top: 12px;
+ z-index: -1;
+}
+
+label.rateGroup {
+ float: left;
+}
+
+div.chartOptions div.dlg-slider {
+ float: left;
+ width: 28em;
+ font-size: 14px;
+}
+
+div.chartOptions div.duration {
+ width: 35em !important;
+}
+
+div.chartOptions .slider {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+input[type="radio"] {
+ margin-top: 0 !important;
+}
+
+div.chartOptions legend {
+ font-size: 1.2em;
+ font-weight: bold;
+ margin-bottom: 10px;
+}
+
+div.chartOptions tab > div {
+ margin-left: 1em;
+}
+
+div.chartOptions span.minicolors-swatch {
+ width: 14px;
+ height: 14px;
+}
+
+.minicolors-input {
+ width: 4em;
+ padding: 0 0 0 24px !important;
+}
+
+div.colorPicker div.colorText {
+ display: inline-block;
+ width: 10em;
+}
+div.colorPicker div:nth-of-type(1), /* first span under div.colorPicker */
+ div.minicolors{
+ float:left;
+ margin-right: 0.5em;
+}
+
+div.chartOptions p.sep {
+ height: 1em;
+}
+
+ul.nav-tabs {
+ border-bottom: 1px solid #ddd !important;
+}
+
+.chartOptions ul.nav-tabs {
+ margin-bottom: 0px !important;
+}
+
+div.tabbable div.tab-content {
+ overflow: visible;
+}
+
+div.tabbable ul.nav-tabs > .active > a {
+ background-color: #f8f8f8;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+
+
+div.tabbable .tab-pane {
+ background-color: #f8f8f8;
+ padding: 12px;
+ border-right: 1px solid #ddd;
+ border-left: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+div.dlg-large div.tabbable .tab-pane {
+ margin-left: 11em;
+}
+
+div.tabbable ul.nav-tabs {
+ margin-bottom: 0;
+}
+
+ul.qdrTopoModes {
+ position: relative;
+ top: -10px;
+}
+.overview.section {
+ /* width: 35em; */
+}
+.overview.section .ngGrid {
+ height: 12em !important;
+ min-height: 12em !important;
+}
+
+.overview.routers.section .ngGrid {
+ height: 16em !important;
+ min-height: 16em !important;
+}
+.overview.routers.section {
+ /*width: 15em; */
+ }
+
+.grid-align-value {
+ text-align: right;
+}
+
+.grid-align-value .ngCellText {
+ padding-right: 10px;
+}
+
+.overview .ngRow:hover {
+ background:#e0e0ff;
+}
+
+.overview-cell .ngCell:hover {
+ background:#e0e0ff;
+}
+.overview-cell .ngCell.col0:hover, .overview-cell .ngCell.col1:hover {
+ background: initial;
+}
+
+
+.qdr-overview.pane.left, .qdr-attributes.pane.left {
+ top: 104px;
+}
+.qdr-topology.pane.left {
+ top: 104px;
+}
+.qdr-overview.pane.left, .qdr-attributes.pane.left, .qdr-topology.pane.left {
+ left: 10px;
+}
+
+.overview-tree, .qdr-attributes {
+ min-height: calc(100vh - 90px)
+}
+/*
+.treeContainer {
+ width: 100%;
+ float: left;
+}
+*/
+.pane-content {
+ overflow: auto;
+}
+
+#entityNames {
+ width: 20em;
+ float: left;
+}
+
+.treeDetails {
+ margin-left: 260px;
+}
+
+.grid:not(.noHighlight) .ui-grid-row:hover .ui-grid-cell-contents {
+ /*background-color: #e0e0ff; */
+ background-color: #e9f2f9
+}
+.ngCellText {
+ padding: 4px 0 0 4px;
+}
+
+.ui-grid-row.ui-grid-row-selected > [ui-grid-row] > .ui-grid-cell {
+ background-color: #e0e0ff;
+}
+
+.tab-content .tab-pane {
+ background-color: #f8f8f8;
+ padding: 12px;
+ border-right: 1px solid #ddd;
+ border-left: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+
+div.chartOptions ul.nav-tabs > .active > a {
+ background-color: #f8f8f8;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+
+div.chartOptions label:nth-of-type(2) {
+ margin-left: 1em;
+}
+div.chartOptions label {
+ font-weight: normal;
+ display: inline-block;
+}
+
+/*
+.form-horizontal .control-label {
+ float: left;
+ width: 160px;
+ padding-top: 5px;
+ text-align: right;
+}
+
+.form-horizontal .controls {
+ margin-left: 180px;
+}
+
+.form-horizontal input, {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+
+input[type="text"], input[type="number"], input[type="password"] {
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+ -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+ transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+input[type="text"], input[type="number"], input[type="password"] {
+ display: inline-block;
+ width: 200px;
+ padding: 4px 6px;
+ margin-bottom: 10px;
+ font-size: 14px;
+ line-height: 20px;
+ color: #555555;
+ vertical-align: middle;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+.login input[type="checkbox"] {
+ margin-top: 0.75em;
+}
+*/
+
+#dispatch-login-container {
+ /* width: 18.5em; */
+ margin-top: 2em;
+}
+/*
+div.login.container {
+ width: 550px;
+}
+*/
+
+
+#overtree .fancytree-container {
+ border: 0px;
+}
+
+#overtree span.fancytree-alert-icon.ui-icon-refresh {
+ background-position: -64px -80px;
+}
+#overtree span.fancytree-alert-icon.ui-icon-transfer-e-w {
+ background-position: -112px -80px;
+}
+
+#alerts {
+ position: fixed;
+ right: 0;
+ top: 0;
+ z-index: 100;
+}
+
+.alert-enter,
+.alert-leave,
+.alert-move {
+ -webkit-transition: 1s linear all;
+ -moz-transition: 1s linear all;
+ -o-transition: 1s linear all;
+ transition: 1s linear all;
+ position:relative;
+}
+
+.alert-enter {
+ left:-10px;
+ opacity:0;
+}
+.alert-enter.alert-enter-active {
+ left:0;
+ opacity:1;
+}
+
+.alert-leave {
+ left:0;
+ opacity:1;
+}
+.alert-leave.alert-leave-active {
+ left:-10px;
+ opacity:0;
+}
+
+.alert-move {
+ opacity:0.5;
+}
+.alert-move.alert-move-active {
+ opacity:1;
+}
+
+.overview .table-striped tr:hover td {
+ background-color: #e0e0ff !important;
+}
+
+#entityNames div.ngViewport {
+ overflow-x: hidden;
+}
+
+.connect-column.connect-form {
+ width: 20em;
+}
+
+.chartLabels button a {
+ text-decoration: none;
+}
+
+.fancytree-ico-c.router .fancytree-icon {
+
+}
+
+.tabs-left .nav-tabs {
+ float: left;
+}
+.tabs-left .nav-tabs > li {
+/* float: initial; */
+}
+
+div.modal.dlg-large {
+ width: 53em;
+}
+
+button.hdash-button a {
+ text-decoration: none;
+ color: #fff;
+}
+
+div.widget-body > div {
+ height: 100%;
+}
+
+div.qdrCharts {
+ height: 100%;
+}
+
+ul.dispatch-view {
+ margin-bottom: 0 !important;
+}
+
+.qdr-overview.pane.left span:not(.fancytree-has-children) .fancytree-icon:before,
+.qdr-attributes.pane.left span:not(.fancytree-has-children) .fancytree-icon:before {
+ color: green;
+}
+
+span:not(.fancytree-has-children).address .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.address .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0ac";
+}
+span:not(.fancytree-has-children).address.mobile .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.address.mobile .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f109";
+}
+span:not(.fancytree-has-children).address.internal.mobile .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.address.internal.mobile .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0ac";
+}
+span:not(.fancytree-has-children).address.router .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.address.router .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f047";
+}
+
+span.address-link .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0ac";
+}
+
+span:not(.fancytree-has-children).connection.external .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f109";
+}
+span:not(.fancytree-has-children).connection.normal .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f08e";
+}
+span:not(.fancytree-has-children).connection.external.quiesced .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f14c";
+ color: red;
+}
+span:not(.fancytree-has-children).connection.inter-router .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f07e";
+}
+span:not(.fancytree-has-children).connection.router-control .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f013";
+}
+span:not(.fancytree-has-children).no-data .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f05e";
+ color: red !important;
+}
+span:not(.fancytree-has-children).loading .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f254";
+}
+span:not(.fancytree-has-children).connector .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f126";
+}
+span:not(.fancytree-has-children).container .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f16c";
+}
+span:not(.fancytree-has-children).log .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0f6";
+}
+span:not(.fancytree-has-children).router\.node .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f013";
+}
+span:not(.fancytree-has-children).link.inter-router .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.link.inter-router .fancytree-icon:before{
+ font-family: FontAwesome;
+ content: "\f07e";
+}
+span:not(.fancytree-has-children).link.router-control .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.link.router-control .fancytree-icon:before{
+ font-family: FontAwesome;
+ content: "\f013";
+}
+span:not(.fancytree-has-children).link.endpoint .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.link.endpoint .fancytree-icon:before{
+ font-family: FontAwesome;
+ content: "\f109";
+}
+span:not(.fancytree-has-children).link.console .fancytree-icon:before,
+span:not(.fancytree-has-children).router\.link.console .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f108";
+}
+span:not(.fancytree-has-children).listener .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f025";
+}
+span:not(.fancytree-has-children).connection .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f07e";
+}
+span:not(.fancytree-has-children).connection.console .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f108";
+}
+span:not(.fancytree-has-children).waypoint .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0ec";
+}
+span:not(.fancytree-has-children).router .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f047";
+}
+span:not(.fancytree-has-children).fixedAddress .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f015";
+}
+span:not(.fancytree-has-children).linkRoutePattern .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f039";
+}
+span:not(.fancytree-has-children).allocator .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f170";
+}
+span:not(.fancytree-has-children).policy .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f256";
+}
+span:not(.fancytree-has-children).logStats .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f080";
+}
+span:not(.fancytree-has-children).autoLink .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0c1";
+}
+span:not(.fancytree-has-children).authServicePlugin .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f0c1";
+}
+span:not(.fancytree-has-children).sslProfile .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f023";
+}
+span:not(.fancytree-has-children).vhost .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f108";
+}
+span:not(.fancytree-has-children).vhostStats .fancytree-icon:before {
+ font-family: FontAwesome;
+ content: "\f080";
+}
+
+
+.ngCellText {
+/* color: #333333; */
+}
+
+.changed {
+ color: #339933;
+}
+
+div.dispatch-router div.help {
+ width: auto;
+ padding: 1em;
+ background-color: lavender;
+ border-radius: 6px;
+ margin-top: 1em;
+ text-align: center;
+}
+
+div.operations tr:nth-child(even) {
+ background: #f3f3f3;
+}
+div.operations tr:nth-child(odd), div.operations tr:last-child {
+ background: #fff;
+}
+
+div.operations tr input {
+ margin: 0;
+ padding: 3px 6px;
+}
+div.operations table {
+ width: 100%;
+}
+div.operations th {
+ width: 50%;
+ border-bottom: 1px solid #cccccc;
+ text-align: left;
+}
+div.operations td:nth-child(odd), div.operations th:nth-child(odd) {
+ border-right: 1px solid #cccccc;
+}
+div.operations td:nth-child(odd) {
+ padding-left: 0;
+}
+div.operations td:nth-child(even), div.operations th:nth-child(even) {
+ padding-left: 5px;
+}
+div.operations th {
+ padding: 5px;
+}
+div.operations .tab-pane.active {
+ padding: 12px 12px 12px 0;
+}
+div.operations label {
+ padding-top: 4px;
+ margin-bottom: 4px;
+}
+.qdrListActions .ngGrid {
+ /*min-height: 40em;
+ height: 100%; */
+}
+div.qdrListActions .ngViewport {
+ height: initial !important;
+}
+
+div.operations .boolean {
+ padding-bottom: 0;
+}
+
+table.log-entry {
+ margin-bottom: 1em;
+ border-top: 1px solid black;
+}
+
+table.log-entry pre {
+ background-color: #f5f5f5;
+ color: inherit;
+ margin: 0;
+ white-space: pre-wrap;
+}
+
+circle.node.normal.console {
+ fill: lightcyan;
+}
+circle.node.artemis {
+ fill: lightgreen;
+}
+circle.node.route-container {
+ fill: orange;
+}
+
+text.console, text.on-demand, text.normal {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-size: 16px;
+}
+
+@font-face {
+ font-family:"Brokers";
+ src: url("brokers.ttf") /* TTF file for CSS3 browsers */
+}
+
+text.artemis {
+ font-family: Brokers;
+ font-size: 20px;
+ font-weight: bold;
+}
+
+text.qpid-cpp {
+ font-family: Brokers;
+ font-size: 18px;
+ font-weight: bold;
+}
+
+i.red {
+ color: red;
+}
+
+.qdrListActions div.delete {
+ width: 20em;
+ margin: auto;
+ border: 1px solid #eaeaea;
+ height: 5em;
+ padding: 4em;
+ background-color: #fcfcfc;
+}
+
+.btn:focus {
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+select:focus, input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus {
+ outline:3px solid rgba(82, 168, 236, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+btn.disabled, .btn[disabled] {
+ opacity: 0.35;
+}
+
+#dispatch-login-container .ng-invalid-range {
+ border-color: #e9322d !important;
+}
+
+div#durationSlider, div#rateSlider {
+ margin-top: 1em;
+}
+
+.list-grid {
+ padding-left: 10px;
+}
+
+.ngViewport.ng-scope {
+ height: auto !important;
+}
+
+.listening-on {
+ background-color: #CCFFCC;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/qdrCharts.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrCharts.html b/console/stand-alone/plugin/html/qdrCharts.html
new file mode 100644
index 0000000..dc57874
--- /dev/null
+++ b/console/stand-alone/plugin/html/qdrCharts.html
@@ -0,0 +1,48 @@
+<!--
+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.
+-->
+
+<style>
+ .page-menu-button {
+ display: none;
+ }
+
+ @media (max-width: 768px) {
+ div.aChart {
+ width: 380px;
+ margin-left: 0;
+ }
+}
+</style>
+<div class="main-display row-fluid qdrCharts" ng-controller="QDR.ChartsController">
+ <div ng-show="hasCharts()">
+ <div ng-repeat="chart in svgCharts" class="chartContainer">
+ <p class="chartLabels">
+ <button ng-click="delChart(chart)" title="Delete"><span class="fa-trash-o"></span></button>
+ <button ng-click="editChart(chart)" title="Configure"><span class="fa-edit"></span></button>
+<!-- <button ng-click="zoomChart(chart)" title="Zoom {{chart.zoomed ? 'in' : 'out'}}" ng-if="!chart.chart.request().nodeList"><i ng-class="chart.zoomed ? 'fa-zoom-in' : 'icon-zoom-out'"></i></button> -->
+ </p><div style="clear:both"></div>
+ <div id="{{chart.chart.id()}}" class="line-chart-pf aChart d3Chart"></div>
+ </div>
+ <div ng-init="chartsLoaded()"></div>
+ </div>
+ <div ng-hide="hasCharts()" class="centered">
+ There are no charts. To add charts to this page, click on a <i class="icon-bar-chart"></i> icon on the <button ng-click="showListPage()"><i class="icon-list "></i> Entities</button> page.
+ </div>
+</div>
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/qdrConnect.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrConnect.html b/console/stand-alone/plugin/html/qdrConnect.html
new file mode 100644
index 0000000..5a4399b
--- /dev/null
+++ b/console/stand-alone/plugin/html/qdrConnect.html
@@ -0,0 +1,87 @@
+<!--
+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.
+-->
+
+<style>
+ .alert {
+ margin-top: 4em;
+ }
+
+</style>
+<div class="row-fluid" ng-controller="QDR.SettingsController">
+ <div class="login" ng-hide="connecting">
+ <div class="col-xs-1"></div>
+ <div class="col-xs-10">
+ <div class="alert alert-success">
+ <p>
+ Enter the address and a HTML-enabled port of a <strong><a href="http://qpid.apache.org/components/dispatch-router/" target="_blank">Qpid Dispatch Router</a></strong>.
+ </p>
+ <!--
+ <p>
+ If Autostart is checked, you will be automatically logged in to the router the next time you start the console.
+ </p> -->
+
+ </div>
+ <div class="connect-form">
+ <form class="form-horizontal no-bottom-margin" novalidate ng-submit="connect()" name="settings">
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="address">Address: </label>
+ <div class="col-md-6">
+ <input tabindex="1" id="address" type="text" ng-model="formEntity.address" placeholder="localhost" name="address" autofocus="autofocus" class="ng-pristine ng-valid ng-valid-required"></div>
+ </div>
+ <div class="form-group">
+ <label tabindex="-1" class="col-md-2 control-label" title="Ports to connect to, by default 5673" for="port">Port: </label>
+ <div class="col-md-6">
+ <input tabindex="2" id="port" posint type="number" placeholder="5673" tooltip="Ports to connect to, by default 5673" ng-model="formEntity.port" name="port" title="Ports to connect to, by default 5673">
+ <span ng-show="settings.port.$error.range">Must be 1 through 65535</span>
+ </div>
+ </div>
+<!--
+ <div class="form-group">
+ <label tabindex="-1" class="col-md-2 control-label" title="Whether or not the connection should be started as soon as you log in" for="autostart">Autostart: </label>
+ <div class="col-md-6">
+ <input tabindex="3" id="autostart" type="checkbox" tooltip="Whether or not the connection should be started as soon as you log into hawtio" ng-model="formEntity.autostart" name="autostart" title="Whether or not the connection should be started as soon as you log into hawtio"
+ class="ng-scope ng-pristine ng-valid"></div>
+ </div>
+-->
+ <input tabindex="-1" type="submit" style="position: absolute; left: -9999px; width: 1px; height: 1px;">
+ <p></p>
+ <div class="form-group">
+ <label tabindex="-1" class="col-md-2 control-label" title="button"></label>
+ <div class="col-md-6">
+ <button type="submit" tabindex="4" class="btn btn-primary pull-left" ng-disabled="settings.$invalid">{{buttonText()}}</button>
+ </div>
+ </div>
+ </form>
+
+
+ </div>
+ </div>
+ <div class="col-xs-1"></div>
+ </div>
+
+ <div class="centered" ng-show="connecting">
+ <i class="icon-spin icon-spinner icon-4x"></i>
+ <p>Please wait, connecting now...</p>
+ </div>
+ <div class="centered error" ng-show="connectionError">
+ <p>There was a connection error: {{connectionErrorText}}</p>
+ </div>
+
+</div>
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/qdrList.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrList.html b/console/stand-alone/plugin/html/qdrList.html
new file mode 100644
index 0000000..61ed0a7
--- /dev/null
+++ b/console/stand-alone/plugin/html/qdrList.html
@@ -0,0 +1,144 @@
+<!--
+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.
+-->
+
+<style>
+@media (min-width: 768px) {
+ .list-grid {
+ padding-left: 300px;
+ }
+}
+@media (max-width: 768px) {
+ .list-grid {
+ padding-left: 0;
+ }
+
+}
+
+span.fancytree-icon {
+ margin-left: 0;
+}
+
+._treeContainer {
+ width: 300px;
+ padding-right: 10px;
+}
+
+.tree-header {
+ z-index: 4;
+}
+
+#list-controller .tree-header {
+ height: auto;
+}
+
+#list-controller select {
+ height: 25px;
+ width: 300px;
+ background-color: #333333;
+}
+</style>
+
+<div id="list-controller" ng-controller="QDR.ListController">
+
+ <div class="page-menu navbar-collapse collapse">
+ <div class="tree-header"><select id="routerSelect"
+ ng-options="node as node.name for node in nodes"
+ ng-model="currentNode"
+ ng-change="selectNode(currentNode)"></select></div>
+ <div class="_treeContainer">
+ <div id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div>
+ <div ng-init="treeReady()"></div>
+ </div>
+ </div>
+
+ <div class="list-grid col-xs-12">
+ <div class="row-fluid qdrListActions">
+ <ul class="nav nav-tabs">
+ <li ng-repeat="mode in modes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html="mode.content"> </li>
+ </ul>
+ <h4>{{selectedRecordName}}</h4>
+ <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
+ <div ng-show="selectedRecordName === selectedEntity" class="no-content">There are no {{selectedEntity | safePlural}}</div>
+ <div id='details-grid' ng-hide="selectedRecordName === selectedEntity" ui-grid="details" ui-grid-resize-columns ui-grid-save-state
+ ng-style="getTableHeight()"></div>
+ </div>
+ <div ng-show="currentMode.id === 'delete'">
+ <div class="delete" ng-show="selectedRecordName !== selectedEntity">
+ <button class="btn btn-primary" ng-click="remove()">Delete</button> {{selectedRecordName}}
+ </div>
+ <div ng-hide="selectedRecordName !== selectedEntity">
+ There are no {{selectedEntity | safePlural}}
+ </div>
+ </div>
+ <div class="operations" ng-show="currentMode.id === 'operations'">
+ <fieldset ng-show="operation != ''">
+ <table>
+ <tr>
+ <th>Attribute</th>
+ <th>Value</th>
+ </tr>
+ <tr title="{{attribute.title}}" ng-repeat="attribute in detailFields">
+ <td><label for="{{attribute.name}}">{{attribute.name | humanify}}</label></td>
+ <!-- we can't do <input type="{angular expression}"> because... jquery throws an exception because... -->
+ <td>
+ <div ng-if="attribute.input == 'input'">
+ <!-- ng-pattern="testPattern(attribute)" -->
+ <div ng-if="attribute.type == 'number'"><input type="number" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.rawValue" ng-required="attribute.required" ng-class="{required: attribute.required, unique: attribute.unique}" class="ui-widget-content ui-corner-all"/><span ng-if="attribute.required" title="required" class="required-indicator"></span><span ng-if="attribute.unique" title="unique" class="unique-indicator"></span></div>
+ <div ng-if="attribute.type == 'text'"><input type="text" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" ng-class="{required: attribute.required, unique: attribute.unique}" class="ui-widget-content ui-corner-all"/><span ng-if="attribute.required" title="required" class="required-indicator"></span><span ng-if="attribute.unique" title="unique" class="unique-indicator"></span></div>
+ <div ng-if="attribute.type == 'textarea'"><textarea name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" ng-class="{required: attribute.required, unique: attribute.unique}" class="ui-widget-content ui-corner-all"></textarea><span ng-if="attribute.required" title="required" class="required-indicator"></span><span ng-if="attribute.unique" title="unique" class="unique-indicator"></span></div>
+ <span ng-if="attribute.type == 'disabled'" >{{getAttributeValue(attribute)}}</span>
+ </div>
+ <div ng-if="attribute.input == 'select'">
+ <select id="{{attribute.name}}" ng-model="attribute.selected" ng-required="attribute.required" ng-class="{required: attribute.required, unique: attribute.unique}" ng-options="item for item in attribute.rawtype track by item"></select>
+ <span ng-if="attribute.required" title="required" class="required-indicator"></span><span ng-if="attribute.unique" title="unique" class="unique-indicator"></span>
+ </div>
+ <div ng-if="attribute.input == 'boolean'" class="boolean">
+ <label><input type="radio" ng-model="attribute.rawValue" ng-value="true"> True</label>
+ <label><input type="radio" ng-model="attribute.rawValue" ng-value="false"> False</label>
+ </div>
+ </td>
+ </tr>
+ <tr><td></td><td><button class="btn btn-primary" type="button" ng-click="ok()">{{operation | Pascalcase}}</button></td></tr>
+ </table>
+ </fieldset>
+ </div>
+ <div ng-show="currentMode.id === 'log'">
+ <div ng-if="logResults.length > 0">
+ <table class="log-entry" ng-repeat="entry in logResults track by $index">
+ <tr>
+ <td align="left" colspan="2">{{entry.time}}</td>
+ </tr>
+ <tr>
+ <td>Type</td><td>{{entry.type}}</td>
+ </tr>
+ <tr>
+ <td>Source</td><td>{{entry.source}}:{{entry.line}}</td>
+ </tr>
+ <tr>
+ <td valign="middle">Message</td><td valign="middle"><pre>{{entry.message}}</pre></td>
+ </tr>
+ </table>
+ </div>
+ <div ng-if="logResults.length == 0 && !fetchingLog">No log entries for {{selectedRecordName}}</div>
+ <div ng-if="fetchingLog">Fetching logs for {{selectedRecordName}}</div>
+ </div>
+ </div>
+ </div>
+</div>
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/qdrOverview.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrOverview.html b/console/stand-alone/plugin/html/qdrOverview.html
new file mode 100644
index 0000000..3b4acb9
--- /dev/null
+++ b/console/stand-alone/plugin/html/qdrOverview.html
@@ -0,0 +1,283 @@
+<!--
+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.
+-->
+
+<style>
+@media (min-width: 768px) {
+ .overview-grid {
+ padding-left: 300px;
+ }
+}
+@media (max-width: 768px) {
+ .overview-grid {
+ padding-left: 0;
+ }
+
+}
+
+span.fancytree-icon {
+ margin-left: 0;
+}
+
+.treeContainer {
+ width: 300px;
+ padding-right: 10px;
+}
+
+ .gridDetails .aChart {
+ margin-left: -1em;
+ margin-right: 2em;
+ width: 400px;
+ }
+
+@media (max-width: 768px) {
+ .gridDetails .aChart {
+ margin-right: 1em;
+ width: 380px;
+ }
+}
+
+.page-menu {
+ top: 0;
+}
+
+.overview-grid {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: calc(100% - 40px);
+ margin-left: 20px;
+}
+
+</style>
+
+<div id="overview-controller" ng-controller="QDR.OverviewController">
+
+ <div class="page-menu navbar-collapse collapse">
+ <div class="treeContainer ng-scope">
+ <div id="overtree"></div>
+ <div ng-init="overviewLoaded()"></div>
+ </div>
+ </div>
+
+ <div class="overview-grid">
+ <div class="gridDetails" ng-include="template.url"></div>
+ </div>
+</div>
+
+<script type="text/ng-template" id="overviewCharts.html">
+ <div ng-controller="QDR.OverviewChartsController" class="clearfix">
+ <div ng-repeat="chart in overviewCharts" class="chartContainer" >
+ <p class="chartLabels">
+ </p><div style="clear:both"></div>
+ <div id="{{chart.chart.id()}}" class="line-chart-pf aChart d3Chart"></div>
+ </div>
+ </div>
+ <div ng-controller="QDR.TopAddressesController">
+ <h1>Most active addresses</h1>
+ <div ng-if="anyAddresses()" id="topAddresses" class="grid" ui-grid="topAddressesGrid" ui-grid-selection ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ <div ng-if="!anyAddresses()">There are no addresses to which messages are being delivered.</div>
+ </div>
+</script>
+
+<!-- the following scripts are content that gets loaded into the above div that has the temple.url -->
+<script type="text/ng-template" id="routers.html">
+ <div class="row-fluid">
+ <h3>Routers</h3>
+ <div class="overview">
+ <div class="grid" ui-grid="allRouters" ui-grid-selection ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+<script type="text/ng-template" id="router.html">
+ <div class="row-fluid">
+ <h3>Router {{router.title}} attributes</h3>
+ <div class="grid noHighlight" ui-grid="routerGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="addresses.html">
+ <div class="row-fluid">
+ <h3>Addresses</h3>
+ <div class="overview">
+ <div class="grid" ui-grid="addressesGrid" ui-grid-selection ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+<script type="text/ng-template" id="address.html">
+ <div class="row-fluid">
+ <ul class="nav nav-tabs">
+ <li ng-repeat="mode in gridModes" ng-click="selectMode(mode,'Address')" ng-class="{active : isModeSelected(mode,'Address')}" title="{{mode.title}}" ng-bind-html="mode.content"> </li>
+ </ul>
+ <div ng-if="isModeVisible('Address','attributes')" class="selectedItems">
+ <h3>Address {{address.title}}</h3>
+ <div class="grid noHighlight" ui-grid="addressGrid" ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ <div ng-if="isModeVisible('Address','links')" class="selectedItems">
+ <h3>Links for address {{address.title}}</h3>
+ <div class="grid" ui-grid="linksGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="links.html">
+ <div class="row-fluid">
+ <h3>Links <button type='button' ng-click="filterToggle()" class='btn btn-secondary btn-filter'>Filter</button></h3>
+ <div id="linkFilter">
+ <div class="filter-title">
+ <h6>Filter links</h6>
+ <button ng-click="filterToggle()" type="button" class="btn btn-primary filter-close">x</button>
+ </div>
+ <div class="formLine"><label for="hideConsoles">Hide console links</label><input name="hideConsoles" id="hideConsoles" type="checkbox" ng-model="filter.hideConsoles"/></div>
+ <div class="formLine"><label for="endpointOnly">Endpoints only</label><input name="linkTypes" id="endpointOnly" type="radio" ng-model="filter.endpointsOnly" value="true"/>
+ <label for="allLinks">All link types</label><input name="linkTypes" id="allLinks" type="radio" ng-model="filter.endpointsOnly" value="false"/></div>
+ </div>
+ <div class="grid" ui-grid="linksGrid" ui-grid-selection ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+</script>
+<script type="text/ng-template" id="link.html">
+ <div class="row-fluid">
+ <h3>Link {{link.title}}</h3>
+ <div class="grid noHighlight" ui-grid="linkGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="connections.html">
+ <div class="row-fluid">
+ <h3>Connections</h3>
+ <div class="overview">
+ <div class="grid" ui-grid="allConnectionGrid" ui-grid-selection ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+<script type="text/ng-template" id="connection.html">
+ <div class="row-fluid">
+ <ul class="nav nav-tabs">
+ <li ng-repeat="mode in gridModes" ng-click="selectMode(mode,'Connection')" ng-class="{active : isModeSelected(mode,'Connection')}" title="{{mode.title}}" ng-bind-html="mode.content"> </li>
+ </ul>
+ <div ng-if="isModeVisible('Connection','attributes')" class="selectedItems">
+ <h3>Connection {{connection.title}}</h3>
+ <div class="grid noHighlight" ui-grid="connectionGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ <div ng-if="isModeVisible('Connection','links')" class="selectedItems">
+ <h3>Links for connection {{connection.title}}</h3>
+ <div class="grid" ui-grid="linksGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="titleHeaderCellTemplate.html">
+ <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
+ <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
+ <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
+ <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
+ <div class="ngSortPriority">{{col.sortPriority}}</div>
+ </div>
+</script>
+<script type="text/ng-template" id="titleCellTemplate.html">
+ <div title="{{row.entity[col.field]}}" class="ngCellText">{{row.entity[col.field]}}</div>
+</script>
+<script type="text/ng-template" id="logs.html">
+ <div class="row-fluid">
+ <h3>Recent log events</h3>
+ <div class="overview">
+ <div class="grid" ui-grid="allLogGrid" ui-grid-selection ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+<script type="text/ng-template" id="logModule.html">
+ <div class="row-fluid">
+ <h3>{{logModule.module}} events</h3>
+ <div class="overview-cell">
+ <div class="grid" ui-grid="logModuleGrid" ui-grid-auto-resize ui-grid-resize-columns ui-grid-save-state></div>
+ </div>
+ </div>
+</script>
+<script type="text/ng-template" id="log.html">
+ <div class="row-fluid">
+ <h3>{{log.title}}</h3>
+ <div ng-if="logFields.length > 0">
+ <table class="log-entry" ng-repeat="entry in logFields track by $index">
+ <tr>
+ <td>Router</td><td>{{entry.nodeId}}</td>
+ </tr>
+ <tr>
+ <td align="left" colspan="2">{{entry.time}}</td>
+ </tr>
+ <tr>
+ <td>Source</td><td>{{entry.source}}:{{entry.line}}</td>
+ </tr>
+ <tr>
+ <td valign="middle">Message</td><td valign="middle"><pre>{{entry.message}}</pre></td>
+ </tr>
+ </table>
+ </div>
+ <div ng-if="logFields.length == 0">No log entries for {{log.title}}</div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="linkRowTemplate.html">
+ <div ng-class="{'linkDirIn': row.entity.linkDir == 'in', 'linkDirOut': row.entity.linkDir == 'out' }">
+ <div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name"
+ class="ui-grid-cell" ng-class="{ 'ui-grid-row-header-cell': col.isRowHeader }" ui-grid-cell>
+ </div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="linkAggTemplate.html">
+ <div ng-click='row.toggleExpand(); saveGroupState()' ng-style='rowStyle(row)' class='ngAggregate ng-scope' style='top: 0px; height: 48px; left: 0px;'>
+ <span class='ngAggregateText ng-binding'>
+ {{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})
+ </span>
+ <div ng-class="{true: 'ngAggArrowCollapsed', false: 'ngAggArrowExpanded'}[row.collapsed]"></div>
+ </div>
+</script>
+
+<script type="text/ng-template" id="viewLogs.html">
+ <div class="modal-header">
+ <h3 class="modal-title">Logs for {{nodeName}} {{module}}:{{level | humanify}}</h3>
+ </div>
+ <div class="modal-body">
+ <div ng-if="loading == false">
+ <div class="log-details" ng-if="logFields.length > 0">
+ <table class="log-entry" ng-repeat="entry in logFields track by $index">
+ <tr>
+ <td>Router</td><td>{{entry.nodeId}}</td>
+ </tr>
+ <tr>
+ <td align="left" colspan="2">{{entry.time}}</td>
+ </tr>
+ <tr>
+ <td>Source</td><td>{{entry.source}}:{{entry.line}}</td>
+ </tr>
+ <tr>
+ <td valign="middle">Message</td><td valign="middle"><pre>{{entry.message}}</pre></td>
+ </tr>
+ </table>
+ </div>
+ <div ng-if="logFields.length == 0">No log entries</div>
+ </div>
+ <div ng-if="loading == true">
+ Loading...
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
+ </div>
+</script>
+
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/qdrSchema.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrSchema.html b/console/stand-alone/plugin/html/qdrSchema.html
new file mode 100644
index 0000000..5bd9207
--- /dev/null
+++ b/console/stand-alone/plugin/html/qdrSchema.html
@@ -0,0 +1,62 @@
+<!--
+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.
+-->
+<style>
+ .page-menu-button {
+ display: none;
+ }
+
+
+span.fancytree-title {
+ color: black;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ width: 16em;
+}
+
+span.fancytree-node:hover {
+ color: white !important;
+ background-color: black !important;
+}
+span.fancytree-node:hover span.fancytree-title {
+ color: white;
+}
+
+span.fancytree-node.fancytree-active:hover,
+span.fancytree-node.fancytree-active:hover span.fancytree-title {
+ color: white;
+}
+
+span.fancytree-expanded.fancytree-has-children, span.fancytree-expanded.fancytree-has-children + ul {
+ background-color: white;
+}
+span.fancytree-expanded.fancytree-has-children:hover {
+ background-color: black;
+}
+
+span.fancytree-node.fancytree-active {
+ background-color: #6aa3d5;
+}
+
+
+</style>
+
+<div class="main-display row-fluid" ng-controller="QDR.SchemaController">
+ <div id="schema"></div>
+</div>
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/qdrTopology.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrTopology.html b/console/stand-alone/plugin/html/qdrTopology.html
new file mode 100644
index 0000000..2b82e34
--- /dev/null
+++ b/console/stand-alone/plugin/html/qdrTopology.html
@@ -0,0 +1,146 @@
+<!--
+Licensed t o 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.
+-->
+
+<style>
+@media (min-width: 768px) {
+ .diagram {
+ padding-left: 300px;
+ }
+ .showLeft {
+ display: block;
+ }
+}
+@media (max-width: 768px) {
+ .diagram {
+ padding-left: 0 !important;
+ }
+ .showLeft {
+ display: none;
+ }
+
+}
+
+button.page-menu-button {
+ display: none;
+}
+ #popover-div {
+ position: absolute;
+ z-index: 200;
+ border-radius: 4px;
+ background-color: black;
+ color: white;
+ opacity: .95;
+ padding: 12px;
+ font-size: 14px;
+ display: none;
+ }
+
+ table.popupTable td {
+ padding-right: 5px;
+ font-size: 10px;
+ }
+
+#topologyForm {
+ border-right: 1px solid lightgray;
+ border-bottom: 1px solid lightgray;
+ padding: 2px;
+ /* position: absolute; */
+ background-color: #333333;
+ width: 300px;
+}
+
+ #topologyForm .infoGrid span {
+ display: inline-block;
+ width: 50%;
+ height: 28px;
+ padding: 3px 6px 2px 6px;
+ border-right: 1px solid #666666;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ #topologyForm .infoGrid span:last-child {
+ text-align: right;
+ border-right: 0;
+ }
+ #topologyForm .infoGrid div {
+ height: 28px;
+ }
+ #topologyForm .infoGrid div.odd {
+ background-color: #444444;
+ }
+
+ #topologyForm .infoGrid div.listening-on {
+ background-color: #336633;
+ }
+ .page-menu {
+ width: 300px;
+ }
+
+</style>
+<div class="qdrTopology row" ng-controller="QDR.TopologyController">
+ <div ng-controller="QDR.TopologyFormController">
+ <div class="page-menu navbar-collapse collapse">
+ <div id="topologyForm">
+ <div>
+ <h4>{{form}} Info</h4>
+ <!-- <div id="routerInfo" class="grid" ui-grid="topoGridRouter" ng-style="infoStyle()"></div> -->
+ <div id="formInfo"></div>
+ </div>
+ </div>
+ <button ng-if="panelVisible" ng-click="hideLeftPane()" class="hideLeft" title="Hide"><i class="icon-step-backward"></i></button>
+ </div>
+ <button ng-if="!panelVisible" ng-click="showLeftPane()" class="showLeft" title="Show"><i class="icon-step-forward"></i></button>
+ </div>
+
+ <div class="diagram col-xs-12">
+ <div id="topology"><!-- d3 toplogy here --></div>
+ <div id="crosssection"></div><div id="crosshtml" ng-bind-html="crosshtml"></div>
+
+ <div id="node_context_menu" class="contextMenu">
+ <ul>
+ <li class="na" ng-class="{'force-display': !isFixed()}" ng-click="setFixed(true)">Freeze in place</li>
+ <li class="na" ng-class="{'force-display': isFixed()}" ng-click="setFixed(false)">Unfreeze</li>
+ </ul>
+ </div>
+ <div id="svg_legend" class="hidden-xs"></div>
+ <div id="multiple_details">
+ <h4 class="grid-title">Connections</h4>
+ <div class="grid" ui-grid="multiDetails" ui-grid-selection ui-grid-auto-resize ng-style="connectionsStyle()"></div>
+ </div>
+ <div id="link_details">
+ <h4 class="grid-title">Links</h4>
+ <div class="grid" ui-grid="linkDetails" ui-grid-auto-resize ng-style="linksStyle()"></div>
+ </div>
+ </div>
+ <div id="popover-div" ng-bind-html="trustedpopoverContent"></div>
+</div>
+
+<script type="text/ng-template" id="titleHeaderCellTemplate.html">
+ <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
+ <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
+ <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
+ <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
+ <div class="ngSortPriority">{{col.sortPriority}}</div>
+ </div>
+</script>
+<script type="text/ng-template" id="titleCellTemplate.html">
+ <div title="{{row.entity.attributeValue}}" class="ui-grid-cell-contents">{{COL_FIELD CUSTOM_FILTERS | pretty}}</div>
+</script>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[3/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js
files that were in the npm repository
Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrOverview.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverview.js b/console/stand-alone/plugin/js/qdrOverview.js
new file mode 100644
index 0000000..709702c
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverview.js
@@ -0,0 +1,1774 @@
+/*
+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.
+*/
+'use strict';
+/* global angular d3 */
+
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+ /**
+ *
+ * Controller that handles the QDR overview page
+ */
+ QDR.module.controller('QDR.OverviewController', ['$scope', 'QDRService', '$location', '$timeout', '$uibModal', 'uiGridConstants', function($scope, QDRService, $location, $timeout, $uibModal) {
+
+ QDR.log.debug('QDR.OverviewControll started with location of ' + $location.path() + ' and connection of ' + QDRService.management.connection.is_connected());
+ let updateIntervalHandle = undefined;
+ const updateInterval = 5000;
+
+ const COLUMNSTATEKEY = 'QDRColumnKey.';
+ const OVERVIEWEXPANDEDKEY = 'QDROverviewExpanded';
+ const OVERVIEWACTIVATEDKEY = 'QDROverviewActivated';
+ const FILTERKEY = 'QDROverviewFilters';
+ const OVERVIEWMODEIDS = 'QDROverviewModeIds';
+ let treeRoot; // the fancytree root node. initialized once log data is received
+
+ // we want attributes to be listed first, so add it at index 0
+ $scope.subLevelTabs = [{
+ content: '<i class="icon-list"></i> Attributes',
+ title: 'View the attribute values on your selection',
+ isValid: function () { return true; },
+ href: function () { return '#/' + QDR.pluginName + '/attributes'; },
+ index: 0
+ },
+ {
+ content: '<i class="icon-leaf"></i> Operations',
+ title: 'Execute operations on your selection',
+ isValid: function () { return true; },
+ href: function () { return '#/' + QDR.pluginName + '/operations'; },
+ index: 1
+ }];
+ $scope.activeTab = $scope.subLevelTabs[0];
+ $scope.setActive = function (nav) {
+ $scope.activeTab = nav;
+ };
+ $scope.isValid = function (nav) {
+ return nav.isValid();
+ };
+ $scope.isActive = function (nav) {
+ return nav == $scope.activeTab;
+ };
+ $scope.linkFields = [];
+ $scope.link = null;
+ $scope.modes = [
+ {title: 'Overview', name: 'Overview', right: false}
+ ];
+
+ $scope.tmplOverviewTree = QDR.templatePath + 'tmplOverviewTree.html';
+ $scope.templates = [
+ { name: 'Charts', url: 'overviewCharts.html'},
+ { name: 'Routers', url: 'routers.html'},
+ { name: 'Router', url: 'router.html'},
+ { name: 'Addresses', url: 'addresses.html'},
+ { name: 'Address', url: 'address.html'},
+ { name: 'Links', url: 'links.html'},
+ { name: 'Link', url: 'link.html'},
+ { name: 'Connections', url: 'connections.html'},
+ { name: 'Connection', url: 'connection.html'},
+ { name: 'Logs', url: 'logs.html'},
+ { name: 'Log', url: 'logModule.html'}
+ ];
+ let topLevelChildren = [];
+
+ var selectRow = function (gridApi) {
+ if (!gridApi.selection)
+ return;
+ gridApi.selection.on.rowSelectionChanged($scope,function(row){
+ let treeKey = row.grid.options.treeKey;
+ if (treeKey && row.entity[treeKey]) {
+ let key = row.entity[treeKey];
+ $('#overtree').fancytree('getTree').activateKey(key);
+ }
+ });
+ };
+ $scope.routerPagingOptions = {
+ pageSizes: [50, 100, 500],
+ pageSize: 50,
+ currentPage: 1
+ };
+ var getPagedData = function (pageSize, page) {
+ $scope.totalRouters = $scope.allRouterFields.length;
+ $scope.allRouters.showFooter = $scope.totalRouters > 50;
+ $scope.pagedRouterFields = $scope.allRouterFields.slice((page - 1) * pageSize, page * pageSize);
+ };
+ $scope.$watch('pagingOptions', function (newVal, oldVal) {
+ if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+ getPagedData($scope.routerPagingOptions.pageSize, $scope.routerPagingOptions.currentPage);
+ }
+ }, true);
+
+ $scope.totalRouters = 0;
+ $scope.allRouterFields = [];
+ $scope.pagedRouterFields = [];
+ $scope.allRouters = {
+ saveKey: 'allRouters',
+ treeKey: 'nodeId',
+ data: 'pagedRouterFields',
+ columnDefs: [
+ {
+ field: 'id',
+ saveKey: 'allRouters',
+ displayName: 'Router'
+ },
+ {
+ field: 'area',
+ displayName: 'Area'
+ },
+ {
+ field: 'mode',
+ displayName: 'Mode'
+ },
+ {
+ field: 'connections',
+ displayName: 'External connections'
+ },
+ {
+ field: 'addrCount',
+ displayName: 'Address count'
+ },
+ {
+ field: 'linkCount',
+ displayName: 'Link count'
+ }
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ jqueryUIDraggable: true,
+ enablePaging: true,
+ showFooter: $scope.totalRouters > 50,
+ totalServerItems: 'totalRouters',
+ pagingOptions: $scope.routerPagingOptions,
+ multiSelect: false,
+ enableSelectAll: false,
+ onRegisterApi: selectRow,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ minRowsToShow: Math.min(QDRService.management.topology.nodeIdList().length, 50),
+ noUnselect: true,
+ };
+
+ // get info for all routers
+ var allRouterInfo = function (node, callback) {
+ let nodes = {};
+ // gets called each node/entity response
+ var gotNode = function (nodeName, entity, response) {
+ if (!nodes[nodeName])
+ nodes[angular.copy(nodeName)] = {};
+ nodes[nodeName][entity] = response;
+ };
+ // send the requests for all connection and router info for all routers
+ QDRService.management.topology.fetchAllEntities([
+ {entity: 'connection', attrs: ['role']},
+ {entity: 'router'}], function () {
+ // we have all the data now in the nodes object
+ let allRouterFields = [];
+ for (let node in nodes) {
+ let connections = 0;
+ for (let i=0; i<nodes[node]['connection'].results.length; ++i) {
+ // we only requested "role" so it will be at [0]
+ if (nodes[node]['connection'].results[i][0] === 'inter-router')
+ ++connections;
+ }
+ let routerRow = {connections: connections, nodeId: node, id: QDRService.management.topology.nameFromId(node)};
+ nodes[node]['router'].attributeNames.forEach( function (routerAttr, i) {
+ if (routerAttr !== 'routerId' && routerAttr !== 'id')
+ routerRow[routerAttr] = nodes[node]['router'].results[0][i];
+ });
+ allRouterFields.push(routerRow);
+ }
+ $scope.allRouterFields = allRouterFields;
+ expandGridToContent('Routers', $scope.allRouterFields.length);
+ getPagedData($scope.routerPagingOptions.pageSize, $scope.routerPagingOptions.currentPage);
+ updateRouterTree(nodeIds);
+ callback(null);
+ }, gotNode);
+ };
+
+ $scope.routerFields = [];
+ $scope.routerGrid = {
+ saveKey: 'routerGrid',
+ data: 'routerFields',
+ columnDefs: [
+ {
+ field: 'attribute',
+ displayName: 'Attribute',
+ saveKey: 'routerGrid',
+ },
+ {
+ field: 'value',
+ displayName: 'Value',
+ }
+ ],
+ jqueryUIDraggable: true,
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ minRowsToShow: 25,
+ multiSelect: false
+ };
+
+ $scope.router = null;
+ // get info for a single router
+ var routerInfo = function (node, callback) {
+ $scope.router = node;
+
+ let routerFields = [];
+ $scope.allRouterFields.some( function (field) {
+ if (field.id === node.title) {
+ Object.keys(field).forEach ( function (key) {
+ let attr = (key === 'connections') ? 'External connections' : key;
+ routerFields.push({attribute: attr, value: field[key]});
+ });
+ return true;
+ }
+ });
+ $scope.routerFields = routerFields;
+ expandGridToContent('Router', $scope.routerFields.length);
+ callback(null);
+ };
+
+ $scope.addressPagingOptions = {
+ pageSizes: [50, 100, 500],
+ pageSize: 50,
+ currentPage: 1
+ };
+ var getAddressPagedData = function (pageSize, page) {
+ $scope.totalAddresses = $scope.addressesData.length;
+ $scope.addressesGrid.showFooter = $scope.totalAddresses > 50;
+ $scope.pagedAddressesData = $scope.addressesData.slice((page - 1) * pageSize, page * pageSize);
+ };
+ $scope.$watch('addressPagingOptions', function (newVal, oldVal) {
+ if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+ getAddressPagedData($scope.addressPagingOptions.pageSize, $scope.addressPagingOptions.currentPage);
+ }
+ }, true);
+
+ $scope.totalAddresses = 0;
+ $scope.pagedAddressesData = [];
+ $scope.addressesData = [];
+ $scope.selectedAddresses = [];
+ $scope.addressesGrid = {
+ saveKey: 'addressesGrid',
+ treeKey: 'uid',
+ data: 'pagedAddressesData',
+ columnDefs: [
+ {
+ field: 'address',
+ saveKey: 'addressesGrid',
+ displayName: 'address'
+ },
+ {
+ field: 'class',
+ displayName: 'class'
+ },
+ {
+ field: 'phase',
+ displayName: 'phase',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'inproc',
+ displayName: 'in-proc'
+ },
+ {
+ field: 'local',
+ displayName: 'local',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'remote',
+ displayName: 'remote',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'in',
+ displayName: 'in',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'out',
+ displayName: 'out',
+ cellClass: 'grid-align-value'
+ }
+ ],
+ enablePaging: true,
+ showFooter: $scope.totalAddresses > 50,
+ totalServerItems: 'totalAddresses',
+ pagingOptions: $scope.addressPagingOptions,
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ multiSelect: false,
+ enableSelectAll: false,
+ onRegisterApi: selectRow,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ noUnselect: true
+ };
+
+ // get info for all addresses
+ var allAddressInfo = function (address, callback) {
+ let nodes = {};
+ // gets called each node/entity response
+ var gotNode = function (nodeName, entity, response) {
+ if (!nodes[nodeName])
+ nodes[nodeName] = {};
+ nodes[nodeName][entity] = angular.copy(response);
+ };
+ var addr_phase = function (addr) {
+ if (!addr)
+ return '-';
+ if (addr[0] == 'M')
+ return addr[1];
+ return '';
+ };
+ var prettyVal = function (val) {
+ return QDRService.utilities.pretty(val || '-');
+ };
+ let addressFields = [];
+ let addressObjs = {};
+ // send the requests for all connection and router info for all routers
+ QDRService.management.topology.fetchAllEntities({entity: 'router.address'}, function () {
+ for (let node in nodes) {
+ let response = nodes[node]['router.address'];
+ response.results.forEach( function (result) {
+ let address = QDRService.utilities.flatten(response.attributeNames, result);
+
+ var addNull = function (oldVal, newVal) {
+ if (oldVal != null && newVal != null)
+ return oldVal + newVal;
+ if (oldVal != null)
+ return oldVal;
+ return newVal;
+ };
+
+ let uid = address.identity;
+ let identity = QDRService.utilities.identity_clean(uid);
+
+ if (!addressObjs[QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity)])
+ addressObjs[QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity)] = {
+ address: QDRService.utilities.addr_text(identity),
+ 'class': QDRService.utilities.addr_class(identity),
+ phase: addr_phase(identity),
+ inproc: address.inProcess,
+ local: address.subscriberCount,
+ remote: address.remoteCount,
+ 'in': address.deliveriesIngress,
+ out: address.deliveriesEgress,
+ thru: address.deliveriesTransit,
+ toproc: address.deliveriesToContainer,
+ fromproc:address.deliveriesFromContainer,
+ uid: uid
+ };
+ else {
+ let sumObj = addressObjs[QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity)];
+ sumObj.inproc = addNull(sumObj.inproc, address.inProcess);
+ sumObj.local = addNull(sumObj.local, address.subscriberCount);
+ sumObj.remote = addNull(sumObj.remote, address.remoteCount);
+ sumObj['in'] = addNull(sumObj['in'], address.deliveriesIngress);
+ sumObj.out = addNull(sumObj.out, address.deliveriesEgress);
+ sumObj.thru = addNull(sumObj.thru, address.deliveriesTransit);
+ sumObj.toproc = addNull(sumObj.toproc, address.deliveriesToContainer);
+ sumObj.fromproc = addNull(sumObj.fromproc, address.deliveriesFromContainer);
+ }
+ });
+ }
+ for (let obj in addressObjs) {
+ addressObjs[obj].inproc = prettyVal(addressObjs[obj].inproc);
+ addressObjs[obj].local = prettyVal(addressObjs[obj].local);
+ addressObjs[obj].remote = prettyVal(addressObjs[obj].remote);
+ addressObjs[obj]['in'] = prettyVal(addressObjs[obj]['in']);
+ addressObjs[obj].out = prettyVal(addressObjs[obj].out);
+ addressObjs[obj].thru = prettyVal(addressObjs[obj].thru);
+ addressObjs[obj].toproc = prettyVal(addressObjs[obj].toproc);
+ addressObjs[obj].fromproc = prettyVal(addressObjs[obj].fromproc);
+ addressFields.push(addressObjs[obj]);
+ }
+ if (addressFields.length === 0)
+ return;
+ // update the grid's data
+ addressFields.sort ( function (a,b) {
+ return a.address + a['class'] < b.address + b['class'] ? -1 : a.address + a['class'] > b.address + b['class'] ? 1 : 0;}
+ );
+ addressFields[0].title = addressFields[0].address;
+ for (let i=1; i<addressFields.length; ++i) {
+ // if this address is the same as the previous address, add a class to the display titles
+ if (addressFields[i].address === addressFields[i-1].address) {
+ addressFields[i-1].title = addressFields[i-1].address + ' (' + addressFields[i-1]['class'] + ')';
+ addressFields[i].title = addressFields[i].address + ' (' + addressFields[i]['class'] + ')';
+ } else
+ addressFields[i].title = addressFields[i].address;
+ }
+ $scope.addressesData = addressFields;
+ expandGridToContent('Addresses', $scope.addressesData.length);
+ getAddressPagedData($scope.addressPagingOptions.pageSize, $scope.addressPagingOptions.currentPage);
+ // repopulate the tree's child nodes
+ updateAddressTree(addressFields);
+ callback(null);
+ }, gotNode);
+ };
+
+ var updateLinkGrid = function ( linkFields ) {
+ // apply the filter
+ let filteredLinks = linkFields.filter( function (link) {
+ let include = true;
+ if ($scope.filter.endpointsOnly === 'true') {
+ if (link.linkType !== 'endpoint')
+ include = false;
+ }
+ if ($scope.filter.hideConsoles) {
+ if (QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link)))
+ include = false;
+ }
+ return include;
+ });
+ QDR.log.info('setting linkFields in updateLinkGrid');
+ $scope.linkFields = filteredLinks;
+ expandGridToContent('Links', $scope.linkFields.length);
+ getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+ // if we have a selected link
+ if ($scope.link) {
+ // find the selected link in the array of all links
+ let links = $scope.linkFields.filter(function (link) {
+ return link.name === $scope.link.data.fields.name;
+ });
+ if (links.length > 0) {
+ // linkInfo() is the function that is called by fancytree when a link is selected
+ // It is passed a fancytree node. We need to simulate that node type to update the link grid
+ linkInfo({data: {title: links[0].title, fields: links[0]}}, function () {$timeout(function (){});});
+ }
+ }
+ };
+
+ // get info for a all links
+ $scope.linkPagingOptions = {
+ pageSizes: [50, 100, 500],
+ pageSize: 50,
+ currentPage: 1
+ };
+ var getLinkPagedData = function (pageSize, page) {
+ $scope.totalLinks = $scope.linkFields.length;
+ $scope.linksGrid.showFooter = $scope.totalLinks > 50;
+ $scope.pagedLinkData = $scope.linkFields.slice((page - 1) * pageSize, page * pageSize);
+ };
+ $scope.$watch('linkPagingOptions', function (newVal, oldVal) {
+ if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+ getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+ }
+ }, true);
+
+ $scope.totalLinks = 0;
+ $scope.pagedLinkData = [];
+ $scope.selectedLinks = [];
+
+ $scope.linksGrid = {
+ saveKey: 'linksGrid',
+ treeKey: 'uid',
+ data: 'pagedLinkData',
+ columnDefs: [
+ {
+ field: 'link',
+ displayName: 'Link',
+ groupable: false,
+ saveKey: 'linksGrid',
+ width: '11%'
+ },
+ {
+ field: 'linkType',
+ displayName: 'Link type',
+ groupable: false,
+ width: '9%'
+ },
+ {
+ field: 'linkDir',
+ displayName: 'Link dir',
+ groupable: false,
+ width: '8%'
+ },
+ {
+ field: 'adminStatus',
+ displayName: 'Admin status',
+ groupable: false,
+ width: '9%'
+ },
+ {
+ field: 'operStatus',
+ displayName: 'Oper status',
+ groupable: false,
+ width: '9%'
+ },
+ {
+ field: 'deliveryCount',
+ displayName: 'Delivery Count',
+ groupable: false,
+ cellClass: 'grid-align-value',
+ width: '11%'
+ },
+ {
+ field: 'rate',
+ displayName: 'Rate',
+ groupable: false,
+ cellClass: 'grid-align-value',
+ width: '8%'
+ },
+ {
+ field: 'uncounts',
+ displayName: 'Outstanding',
+ groupable: false,
+ cellClass: 'grid-align-value',
+ width: '9%'
+ },
+ {
+ field: 'owningAddr',
+ displayName: 'Address',
+ groupable: false,
+ width: '15%'
+ }/*,
+ {
+ displayName: 'Quiesce',
+ cellClass: 'gridCellButton',
+ cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row, $event)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>',
+ width: '10%'
+ }*/
+ ],
+ enablePaging: true,
+ showFooter: $scope.totalLinks > 50,
+ totalServerItems: 'totalLinks',
+ pagingOptions: $scope.linkPagingOptions,
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ enableColumnReordering: true,
+ showColumnMenu: true,
+ rowTemplate: 'linkRowTemplate.html',
+ minRowsToShow: 15,
+ multiSelect: false,
+ enableSelectAll: false,
+ onRegisterApi: selectRow,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ noUnselect: true
+ };
+
+ $scope.$on('ngGridEventColumns', function (e, columns) {
+ let saveInfo = columns.map( function (col) {
+ return [col.width, col.visible];
+ });
+ let saveKey = columns[0].colDef.saveKey;
+ if (saveKey)
+ localStorage.setItem(COLUMNSTATEKEY+saveKey, JSON.stringify(saveInfo));
+ });
+
+ var allLinkInfo = function (node, callback) {
+ var gridCallback = function (linkFields) {
+ QDRService.management.topology.ensureAllEntities({entity: 'connection', force: true}, function () {
+ // only update the grid with these fields if the List tree node is selected
+ // this is becuase we are using the link grid in other places and we don't want to overwrite it
+ if ($scope.template.name === 'Links')
+ updateLinkGrid(linkFields);
+ updateLinkTree(linkFields);
+ callback(null);
+ });
+ };
+ getAllLinkFields([gridCallback]);
+ };
+
+ var getAllLinkFields = function (completionCallbacks, selectionCallback) {
+ if (!$scope.linkFields) {
+ QDR.log.debug('$scope.linkFields was not defined!');
+ return;
+ }
+ let nodeIds = QDRService.management.topology.nodeIdList();
+ let linkFields = [];
+ let now = Date.now();
+ var rate = function (linkName, response, result) {
+ if (!$scope.linkFields)
+ return 0;
+ let oldname = $scope.linkFields.filter(function (link) {
+ return link.link === linkName;
+ });
+ if (oldname.length === 1) {
+ let elapsed = (now - oldname[0].timestamp) / 1000;
+ if (elapsed < 0)
+ return 0;
+ let delivered = QDRService.utilities.valFor(response.attributeNames, result, 'deliveryCount') - oldname[0].rawDeliveryCount;
+ //QDR.log.debug("elapsed " + elapsed + " delivered " + delivered)
+ return elapsed > 0 ? parseFloat(Math.round((delivered/elapsed) * 100) / 100).toFixed(2) : 0;
+ } else {
+ //QDR.log.debug("unable to find old linkName")
+ return 0;
+ }
+ };
+ let expected = nodeIds.length;
+ let received = 0;
+ var gotLinkInfo = function (nodeName, entity, response) {
+ if (response.results)
+ response.results.forEach( function (result) {
+ var prettyVal = function (field) {
+ let fieldIndex = response.attributeNames.indexOf(field);
+ if (fieldIndex < 0) {
+ return '-';
+ }
+ let val = result[fieldIndex];
+ return QDRService.utilities.pretty(val);
+ };
+ var uncounts = function () {
+ let und = QDRService.utilities.valFor(response.attributeNames, result, 'undeliveredCount');
+ let uns = QDRService.utilities.valFor(response.attributeNames, result, 'unsettledCount');
+ return QDRService.utilities.pretty(und + uns);
+ };
+ var getLinkName = function () {
+ let namestr = QDRService.management.topology.nameFromId(nodeName);
+ return namestr + ':' + QDRService.utilities.valFor(response.attributeNames, result, 'identity');
+ };
+ var fixAddress = function () {
+ let addresses = [];
+ let owningAddr = QDRService.utilities.valFor(response.attributeNames, result, 'owningAddr') || '';
+ let rawAddress = owningAddr;
+ /*
+ - "L*" => "* (local)"
+ - "M0*" => "* (direct)"
+ - "M1*" => "* (dequeue)"
+ - "MX*" => "* (phase X)"
+ */
+ let address = undefined;
+ let starts = {'L': '(local)', 'M0': '(direct)', 'M1': '(dequeue)'};
+ for (let start in starts) {
+ if (owningAddr.startsWith(start)) {
+ let ends = owningAddr.substr(start.length);
+ address = ends + ' ' + starts[start];
+ rawAddress = ends;
+ break;
+ }
+ }
+ if (!address) {
+ // check for MX*
+ if (owningAddr.length > 3) {
+ if (owningAddr[0] === 'M') {
+ let phase = parseInt(owningAddr.substr(1));
+ if (phase && !isNaN(phase)) {
+ let phaseStr = phase + '';
+ let star = owningAddr.substr(2 + phaseStr.length);
+ address = star + ' ' + '(phase ' + phaseStr + ')';
+ }
+ }
+ }
+ }
+ addresses[0] = address || owningAddr;
+ addresses[1] = rawAddress;
+ return addresses;
+ };
+ if (!selectionCallback || selectionCallback(response, result)) {
+ let adminStatus = QDRService.utilities.valFor(response.attributeNames, result, 'adminStatus');
+ let operStatus = QDRService.utilities.valFor(response.attributeNames, result, 'operStatus');
+ let linkName = getLinkName();
+ let linkType = QDRService.utilities.valFor(response.attributeNames, result, 'linkType');
+ let addresses = fixAddress();
+ let link = QDRService.utilities.flatten(response.attributeNames, result);
+ linkFields.push({
+ link: linkName,
+ title: linkName,
+ uncounts: uncounts(),
+ operStatus: operStatus,
+ adminStatus:adminStatus,
+ owningAddr: addresses[0],
+
+ acceptedCount: prettyVal('acceptedCount'),
+ modifiedCount: prettyVal('modifiedCount'),
+ presettledCount: prettyVal('presettledCount'),
+ rejectedCount: prettyVal('rejectedCount'),
+ releasedCount: prettyVal('releasedCount'),
+ deliveryCount:prettyVal('deliveryCount') + ' ',
+
+ rate: QDRService.utilities.pretty(rate(linkName, response, result)),
+ capacity: link.capacity,
+ undeliveredCount: link.undeliveredCount,
+ unsettledCount: link.unsettledCount,
+
+ rawAddress: addresses[1],
+ rawDeliveryCount: link.deliveryCount,
+ name: link.name,
+ linkName: link.linkName,
+ connectionId: link.connectionId,
+ linkDir: link.linkDir,
+ linkType: linkType,
+ peer: link.peer,
+ type: link.type,
+
+ uid: linkName,
+ timestamp: now,
+ nodeId: nodeName,
+ identity: link.identity,
+ });
+ }
+ });
+ if (expected === ++received) {
+ linkFields.sort ( function (a,b) { return a.link < b.link ? -1 : a.link > b.link ? 1 : 0;});
+ completionCallbacks.forEach( function (cb) {
+ cb(linkFields);
+ });
+ }
+ };
+ nodeIds.forEach( function (nodeId) {
+ QDRService.management.topology.fetchEntity(nodeId, 'router.link', [], gotLinkInfo);
+ });
+ };
+
+ $scope.connectionPagingOptions = {
+ pageSizes: [50, 100, 500],
+ pageSize: 50,
+ currentPage: 1
+ };
+ var getConnectionPagedData = function (pageSize, page) {
+ $scope.totalConnections = $scope.allConnectionFields.length;
+ $scope.allConnectionGrid.showFooter = $scope.totalConnections > 50;
+ $scope.pagedConnectionsData = $scope.allConnectionFields.slice((page - 1) * pageSize, page * pageSize);
+ };
+ $scope.$watch('connectionPagingOptions', function (newVal, oldVal) {
+ if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
+ getConnectionPagedData($scope.connectionPagingOptions.pageSize, $scope.connectionPagingOptions.currentPage);
+ }
+ }, true);
+
+ $scope.totalConnections = 0;
+ $scope.pagedConnectionsData = [];
+ $scope.allConnectionFields = [];
+ $scope.allConnectionSelections = [];
+ $scope.allConnectionGrid = {
+ saveKey: 'allConnGrid',
+ treeKey: 'uid',
+ data: 'pagedConnectionsData',
+ columnDefs: [
+ {
+ field: 'host',
+ saveKey: 'allConnGrid',
+ displayName: 'host'
+ },
+ {
+ field: 'container',
+ displayName: 'container'
+ },
+ {
+ field: 'role',
+ displayName: 'role'
+ },
+ {
+ field: 'dir',
+ displayName: 'dir'
+ },
+ {
+ field: 'security',
+ displayName: 'security'
+ },
+ {
+ field: 'authentication',
+ displayName: 'authentication'
+ },
+ ],
+ enablePaging: true,
+ showFooter: $scope.totalConnections > 50,
+ totalServerItems: 'totalConnections',
+ pagingOptions: $scope.connectionPagingOptions,
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ multiSelect: false,
+ enableSelectAll: false,
+ onRegisterApi: selectRow,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ noUnselect: true
+ };
+ // get info for a all connections
+ var allConnectionInfo = function (connection, callback) {
+ getAllConnectionFields([updateConnectionGrid, updateConnectionTree, function () {callback(null);}]);
+ };
+ // called after conection data is available
+ var updateConnectionGrid = function (connectionFields) {
+ $scope.allConnectionFields = connectionFields;
+ expandGridToContent('Connections', $scope.allConnectionFields.length);
+ getConnectionPagedData($scope.connectionPagingOptions.pageSize, $scope.connectionPagingOptions.currentPage);
+ };
+
+ // get the connection data for all nodes
+ // called periodically
+ // creates a connectionFields array and calls the callbacks (updateTree and updateGrid)
+ var getAllConnectionFields = function (callbacks) {
+ let nodeIds = QDRService.management.topology.nodeIdList();
+ let connectionFields = [];
+ let expected = nodeIds.length;
+ let received = 0;
+ let gotConnectionInfo = function (nodeName, entity, response) {
+ response.results.forEach( function (result) {
+
+ let auth = 'no_auth';
+ let connection = QDRService.utilities.flatten(response.attributeNames, result);
+ let sasl = connection.sasl;
+ if (connection.isAuthenticated) {
+ auth = sasl;
+ if (sasl === 'ANONYMOUS')
+ auth = 'anonymous-user';
+ else {
+ if (sasl === 'GSSAPI')
+ sasl = 'Kerberos';
+ if (sasl === 'EXTERNAL')
+ sasl = 'x.509';
+ auth = connection.user + '(' + connection.sslCipher + ')';
+ }
+ }
+
+ let sec = 'no-security';
+ if (connection.isEncrypted) {
+ if (sasl === 'GSSAPI')
+ sec = 'Kerberos';
+ else
+ sec = connection.sslProto + '(' + connection.sslCipher + ')';
+ }
+
+ let host = connection.host;
+ let connField = {
+ host: host,
+ security: sec,
+ authentication: auth,
+ routerId: nodeName,
+ uid: host + connection.container + connection.identity
+ };
+ response.attributeNames.forEach( function (attribute, i) {
+ connField[attribute] = result[i];
+ });
+ connectionFields.push(connField);
+ });
+ if (expected === ++received) {
+ connectionFields.sort ( function (a,b) { return a.host+a.container+a.identity < b.host+b.container+b.identity ?
+ -1 : a.host+a.container+a.identity > b.host+b.container+b.identity ? 1 : 0;});
+ callbacks.forEach( function (cb) {
+ cb(connectionFields);
+ });
+ }
+ };
+ nodeIds.forEach( function (nodeId) {
+ QDRService.management.topology.fetchEntity(nodeId, 'connection', [], gotConnectionInfo);
+ });
+ };
+
+ $scope.addressFields = [];
+ $scope.addressGrid = {
+ saveKey: 'addGrid',
+ data: 'addressFields',
+ columnDefs: [
+ {
+ field: 'attribute',
+ displayName: 'Attribute',
+ saveKey: 'addGrid',
+ width: '40%'
+ },
+ {
+ field: 'value',
+ displayName: 'Value',
+ width: '40%'
+ }
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ minRowsToShow: 11,
+ jqueryUIDraggable: true,
+ multiSelect: false
+ };
+
+ // get info for a single address
+ var addressInfo = function (address, callback) {
+ $scope.address = address;
+ let currentEntity = getCurrentLinksEntity();
+ // we are viewing the addressLinks page
+ if (currentEntity === 'Address' && entityModes[currentEntity].currentModeId === 'links') {
+ updateModeLinks();
+ callback(null);
+ return;
+ }
+
+ $scope.addressFields = [];
+ let fields = Object.keys(address.data.fields);
+ fields.forEach( function (field) {
+ if (field != 'title' && field != 'uid')
+ $scope.addressFields.push({attribute: field, value: address.data.fields[field]});
+ });
+ expandGridToContent('Address', $scope.addressFields.length);
+ callback(null);
+ };
+
+ $scope.singleLinkFields = [];
+ $scope.linkGrid = {
+ saveKey: 'linkGrid',
+ data: 'singleLinkFields',
+ columnDefs: [
+ {
+ field: 'attribute',
+ displayName: 'Attribute',
+ width: '40%'
+ },
+ {
+ field: 'value',
+ displayName: 'Value',
+ width: '40%'
+ }
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ minRowsToShow: 24,
+ jqueryUIDraggable: true,
+ multiSelect: false
+ };
+
+ // display the grid detail info for a single link
+ var linkInfo = function (link, callback) {
+ if (!link) {
+ callback(null);
+ return;
+ }
+ $scope.link = link;
+
+ $scope.singleLinkFields = [];
+ let fields = Object.keys(link.data.fields);
+ let excludeFields = ['title', 'uid', 'uncounts', 'rawDeliveryCount', 'timestamp', 'rawAddress'];
+ fields.forEach( function (field) {
+ if (excludeFields.indexOf(field) == -1)
+ $scope.singleLinkFields.push({attribute: field, value: link.data.fields[field]});
+ });
+ expandGridToContent('Link', $scope.singleLinkFields.length);
+ callback(null);
+ };
+
+ // get info for a single connection
+ $scope.gridModes = [{
+ content: '<a><i class="icon-list"></i> Attributes</a>',
+ id: 'attributes',
+ title: 'View attributes'
+ },
+ {
+ content: '<a><i class="icon-link"></i> Links</a>',
+ id: 'links',
+ title: 'Show links'
+ }
+ ];
+ var saveModeIds = function () {
+ let modeIds = {Address: entityModes.Address.currentModeId, Connection: entityModes.Connection.currentModeId};
+ localStorage[OVERVIEWMODEIDS] = JSON.stringify(modeIds);
+ };
+ var loadModeIds = function () {
+ return angular.fromJson(localStorage[OVERVIEWMODEIDS]) ||
+ {Address: 'attributes', Connection: 'attributes'};
+ };
+ var savedModeIds = loadModeIds();
+ let entityModes = {
+ Address: {
+ currentModeId: savedModeIds.Address,
+ filter: function (response, result) {
+ let owningAddr = QDRService.utilities.valFor(response.attributeNames, result, 'owningAddr');
+ return (owningAddr === $scope.address.data.fields.uid);
+ }
+ },
+ Connection: {
+ currentModeId: savedModeIds.Connection,
+ filter: function (response, result) {
+ let connectionId = QDRService.utilities.valFor(response.attributeNames, result, 'connectionId');
+ return (connectionId === $scope.connection.data.fields.identity);
+ }
+ }
+ };
+ $scope.selectMode = function (mode, entity) {
+ if (!mode || !entity)
+ return;
+ entityModes[entity].currentModeId = mode.id;
+ saveModeIds();
+ if (mode.id === 'links') {
+ $scope.linkFields = [];
+ getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+ updateModeLinks();
+ }
+ updateExpanded();
+ };
+ $scope.isModeSelected = function (mode, entity) {
+ return mode.id === entityModes[entity].currentModeId;
+ };
+ $scope.isModeVisible = function (entity, id) {
+ return entityModes[entity].currentModeId === id;
+ };
+
+ var updateEntityLinkGrid = function (linkFields) {
+ $scope.linkFields = linkFields;
+ expandGridToContent('Link', $scope.linkFields.length);
+ getLinkPagedData($scope.linkPagingOptions.pageSize, $scope.linkPagingOptions.currentPage);
+ };
+ // based on which entity is selected, get and filter the links
+ var updateModeLinks = function () {
+ let currentEntity = getCurrentLinksEntity();
+ if (!currentEntity)
+ return;
+ getAllLinkFields([updateEntityLinkGrid], entityModes[currentEntity].filter);
+ };
+ var getCurrentLinksEntity = function () {
+ let currentEntity;
+ let active = $('#overtree').fancytree('getActiveNode');
+ if (active) {
+ currentEntity = active.type ? active.type : active.data.fields.type;
+ }
+ return currentEntity;
+ };
+
+ $scope.quiesceLinkClass = function (row) {
+ let stateClassMap = {
+ enabled: 'btn-primary',
+ disabled: 'btn-danger'
+ };
+ return stateClassMap[row.entity.adminStatus];
+ };
+
+ $scope.quiesceLink = function (row, $event) {
+ $event.stopPropagation();
+ QDRService.management.topology.quiesceLink(row.entity.nodeId, row.entity.name)
+ .then( function (results, context) {
+ let statusCode = context.message.application_properties.statusCode;
+ if (statusCode < 200 || statusCode >= 300) {
+ QDR.Core.notification('error', context.message.statusDescription);
+ QDR.log.error('Error ' + context.message.statusDescription);
+ }
+ });
+ };
+
+ $scope.quiesceLinkDisabled = function (row) {
+ return (row.entity.operStatus !== 'up' && row.entity.operStatus !== 'down');
+ };
+ $scope.quiesceLinkText = function (row) {
+ return row.entity.adminStatus === 'disabled' ? 'Revive' : 'Quiesce';
+ };
+
+ $scope.expandAll = function () {
+ $('#overtree').fancytree('getRoot').visit(function(node){
+ node.expand(true);
+ });
+ };
+ $scope.contractAll = function () {
+ $('#overtree').fancytree('getRoot').visit(function(node){
+ node.expand(false);
+ });
+ };
+ $scope.connectionFields = [];
+ $scope.connectionGrid = {
+ saveKey: 'connGrid',
+ data: 'connectionFields',
+ columnDefs: [
+ {
+ field: 'attribute',
+ displayName: 'Attribute',
+ saveKey: 'connGrid',
+ width: '40%'
+ },
+ {
+ field: 'value',
+ displayName: 'Value',
+ width: '40%'
+ }
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ minRowsToShow: 21,
+ jqueryUIDraggable: true,
+ multiSelect: false
+ };
+
+ var connectionInfo = function (connection, callback) {
+ if (!connection) {
+ callback(null);
+ return;
+ }
+ $scope.connection = connection;
+
+ let currentEntity = getCurrentLinksEntity();
+ // we are viewing the connectionLinks page
+ if (currentEntity === 'Connection' && entityModes[currentEntity].currentModeId === 'links') {
+ updateModeLinks();
+ callback(null);
+ return;
+ }
+
+ $scope.connectionFields = [];
+ for (let field in connection.data.fields) {
+ if (field != 'title' && field != 'uid')
+ $scope.connectionFields.push({attribute: field, value: connection.data.fields[field]});
+ }
+ expandGridToContent('Connection', $scope.connectionFields.length);
+ callback(null);
+ };
+
+
+ const logModuleCellTemplate = '<div class="ui-grid-cell-contents" ng-click="grid.appScope.logInfoFor(row, col)">{{COL_FIELD CUSTOM_FILTERS | pretty}}</div>';
+
+ $scope.logModule = {};
+ $scope.logModuleSelected = [];
+ $scope.logModuleData = [];
+ $scope.logModuleGrid = {
+ data: 'logModuleData',
+ columnDefs: [
+ {
+ field: 'nodeName',
+ displayName: 'Router',
+ },
+ {
+ field: 'enable',
+ displayName: 'Enable level',
+ },
+ {
+ field: 'noticeCount',
+ displayName: 'Notice',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'infoCount',
+ displayName: 'Info',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'traceCount',
+ displayName: 'Trace',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'debugCount',
+ displayName: 'Debug',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'warningCount',
+ displayName: 'Warning',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'errorCount',
+ displayName: 'Error',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'criticalCount',
+ displayName: 'Critical',
+ cellTemplate: logModuleCellTemplate,
+ cellClass: 'grid-align-value',
+ },
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ jqueryUIDraggable: true,
+ multiSelect: false,
+ enableSelectAll: false,
+ onRegisterApi: selectRow,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ minRowsToShow: Math.min(QDRService.management.topology.nodeIdList().length, 50),
+ noUnselect: true
+ };
+
+ $scope.logInfoFor = function (row, col) {
+ logDialog(row, col);
+ };
+
+ function logDialog(row, col) {
+ let d = $uibModal.open({
+ animation: true,
+ templateUrl: 'viewLogs.html',
+ controller: 'QDR.OverviewLogsController',
+ resolve: {
+ nodeName: function () {
+ return row.entity.nodeName;
+ },
+ module: function () {
+ return row.entity.name;
+ },
+ level: function () {
+ return col.displayName;
+ },
+ nodeId: function () {
+ return row.entity.nodeId;
+ },
+ }
+ });
+ d.result.then(function () {
+ QDR.log.debug('d.open().then');
+ }, function () {
+ QDR.log.debug('Modal dismissed at: ' + new Date());
+ });
+ }
+
+ const numberTemplate = '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{COL_FIELD | pretty}}</span></div>';
+ $scope.allLogFields = [];
+ $scope.allLogSelections = [];
+ $scope.allLogGrid = {
+ saveKey: 'allLogGrid',
+ treeKey: 'name',
+ data: 'allLogFields',
+ columnDefs: [
+ {
+ field: 'name',
+ saveKey: 'allLogGrid',
+ displayName: 'Module'
+ },
+ {
+ field: 'noticeCount',
+ displayName: 'Notice',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'infoCount',
+ displayName: 'Info',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'traceCount',
+ displayName: 'Trace',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'debugCount',
+ displayName: 'Debug',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'warningCount',
+ displayName: 'Warning',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'errorCount',
+ displayName: 'Error',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ {
+ field: 'criticalCount',
+ displayName: 'Critical',
+ cellTemplate: numberTemplate,
+ cellClass: 'grid-align-value',
+ },
+ ],
+ //enableCellSelection: true,
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ minRowsToShow: 17,
+ multiSelect: false,
+ enableSelectAll: false,
+ onRegisterApi: selectRow,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ noUnselect: true
+ };
+
+ let allLogEntries = {};
+ var allLogInfo = function (log, callback) {
+ // update the count of entries for each module
+ $scope.allLogFields = [];
+ let logResults = {};
+ let logDetails = {};
+
+ var gotLogStats = function (node, entity, response) {
+ logDetails[node] = [];
+ response.results.forEach( function (result) {
+ let oresult = QDRService.utilities.flatten(response.attributeNames, result);
+ // make a copy for the details grid since logResults has the same object reference
+ logDetails[node].push(angular.copy(oresult));
+ if (!(oresult.name in logResults)) {
+ logResults[oresult.name] = oresult;
+ }
+ else {
+ response.attributeNames.forEach( function (attr, i) {
+ if (attr.substr(attr.length-5) === 'Count') {
+ logResults[oresult.name][attr] += result[i];
+ }
+ });
+ }
+ });
+ };
+ var gotAllLogStats = function () {
+ let sortedModules = Object.keys(logResults);
+ sortedModules.sort(function (a,b) {return a<b?-1:a>b?1:0;});
+ $scope.allLogFields = [];
+ sortedModules.forEach( function (module) {
+ $scope.allLogFields.push(logResults[module]);
+ });
+ expandGridToContent('Logs', $scope.allLogFields.length);
+ allLogEntries = logDetails;
+ updateLogTree($scope.allLogFields);
+ callback(null);
+ };
+ QDRService.management.topology.fetchAllEntities({entity: 'logStats'}, gotAllLogStats, gotLogStats);
+ };
+
+ var expandGridToContent = function (type, rows) {
+ let tree = $('#overtree').fancytree('getTree');
+ let node = null;
+ if (tree && tree.getActiveNode)
+ node = tree.getActiveNode();
+ if (node) {
+ if (node.type === type || node.data.type === type) {
+ let height = (rows+1) * 30 + 46; // header is 40px
+ let gridDetails = $('#overview-controller .grid');
+ gridDetails.css('height', height + 'px');
+ }
+ }
+ };
+
+ $scope.logFields = [];
+ // get info for a single log
+ var logInfo = function (node, callback) {
+
+ var gotLogInfo = function (responses) {
+ $scope.logModuleData = [];
+ $scope.logModule.module = node.key;
+ for (let n in responses) {
+ let moduleIndex = responses[n]['log'].attributeNames.indexOf('module');
+ let result = responses[n]['log'].results.filter( function (r) {
+ return r[moduleIndex] === node.key;
+ })[0];
+ let logInfo = QDRService.utilities.flatten(responses[n]['log'].attributeNames, result);
+ let entry = allLogEntries[n];
+ entry.forEach( function (module) {
+ if (module.name === node.key) {
+ module.nodeName = QDRService.management.topology.nameFromId(n);
+ module.nodeId = n;
+ module.enable = logInfo.enable;
+ $scope.logModuleData.push(module);
+ }
+ });
+ }
+ $scope.logModuleData.sort ( function (a,b) { return a.nodeName < b.nodeName? -1 : a.nodeName> b.nodeName? 1 : 0;});
+ expandGridToContent('Log', $scope.logModuleData.length);
+ callback(null);
+ };
+ QDRService.management.topology.fetchAllEntities({entity: 'log', attrs: ['module', 'enable']}, gotLogInfo);
+ };
+
+ var getExpandedList = function () {
+ if (!treeRoot)
+ return;
+ let list = [];
+ if (treeRoot.visit) {
+ treeRoot.visit(function(node){
+ if (node.isExpanded()) {
+ list.push(node.data.parentKey);
+ }
+ });
+ }
+ return list;
+ };
+
+ // loads the tree node name that was last selected
+ var loadActivatedNode = function () {
+ return localStorage[OVERVIEWACTIVATEDKEY] || 'Routers';
+ };
+ // saved the tree node name that is currently selected
+ var saveActivated = function (key) {
+ localStorage[OVERVIEWACTIVATEDKEY] = key;
+ };
+ // loads list that was saved
+ var loadExpandedNodeList = function () {
+ return angular.fromJson(localStorage[OVERVIEWEXPANDEDKEY]) || [];
+ };
+ // called when a node is expanded
+ // here we save the expanded node so it can be restored when the page reloads
+ var saveExpanded = function () {
+ let list = getExpandedList();
+ localStorage[OVERVIEWEXPANDEDKEY] = JSON.stringify(list);
+ expandedNodeList = list;
+ };
+
+ var setTemplate = function (node) {
+ let type = node.type;
+ let template = $scope.templates.filter( function (tpl) {
+ return tpl.name == type;
+ });
+ $timeout( function () {
+ $scope.template = template[0];
+ });
+ };
+ // activated is called each time a tree node is clicked
+ // based on which node is clicked, load the correct data grid template and start getting the data
+ var onTreeNodeActivated = function (event, data) {
+ saveExpanded();
+ saveActivated(data.node.key);
+ $scope.ActivatedKey = data.node.key;
+ setTemplate(data.node);
+ updateExpanded();
+ };
+
+ var onTreeNodeExpanded = function () {
+ saveExpanded();
+ updateExpanded();
+ };
+
+ if (!QDRService.management.connection.is_connected()) {
+ QDR.redirectWhenConnected($location, 'overview');
+ return;
+ }
+ $scope.template = $scope.templates[0];
+
+ /* --------------------------------------------------
+ *
+ * setup the tree on the left
+ *
+ * -------------------------------------------------
+ */
+ var getActiveChild = function (node) {
+ let active = node.children.filter(function (child) {
+ return child.isActive();
+ });
+ if (active.length > 0)
+ return active[0].key;
+ return null;
+ };
+ // utility function called by each top level tree node when it needs to populate its child nodes
+ var updateLeaves = function (leaves, entity, worker) {
+ let tree = $('#overtree').fancytree('getTree'), node;
+ if (tree && tree.getNodeByKey) {
+ node = tree.getNodeByKey(entity);
+ }
+ if (!tree || !node) {
+ return;
+ }
+ let wasActive = node.isActive();
+ let wasExpanded = node.isExpanded();
+ let activeChildKey = getActiveChild(node);
+ node.removeChildren();
+ let children = [];
+ leaves.forEach( function (leaf) {
+ children.push(worker(leaf));
+ });
+ node.addNode(children);
+ // top level node was expanded
+ if (wasExpanded)
+ node.setExpanded(true, {noAnimation: true, noEvents: true});
+ if (wasActive) {
+ node.setActive(true, {noAnimation: true, noEvents: true});
+ } else {
+ // re-active the previously active child node
+ if (activeChildKey) {
+ let newNode = tree.getNodeByKey(activeChildKey);
+ // the node may not be there after the update
+ if (newNode)
+ newNode.setActive(true, {noAnimation: true, noEvents: true}); // fires the onTreeNodeActivated event for this node
+ }
+ }
+ resizer();
+ };
+
+ var resizer = function () {
+ // this forces the tree and the grid to be the size of the browser window.
+ // the effect is that the tree and the grid will have vertical scroll bars if needed.
+ // the alternative is to let the tree and grid determine the size of the page and have
+ // the scroll bar on the window
+ //let viewport = $('#overview-controller .pane-viewport');
+ //viewport.height( window.innerHeight - viewport.offset().top);
+
+ // don't allow HTML in the tree titles
+ $('.fancytree-title').each( function () {
+ let unsafe = $(this).html();
+ $(this).html(unsafe.replace(/</g, '<').replace(/>/g, '>'));
+ });
+
+ // remove the comments to allow the tree to take all the height it needs
+
+ let gridDetails = $('#overview-controller .grid');
+ if (gridDetails.offset())
+ gridDetails.height( window.innerHeight - gridDetails.offset().top);
+
+ let gridViewport = $('#overview-controller .ui-grid-viewport');
+ if (gridViewport.offset())
+ gridViewport.height( window.innerHeight - gridViewport.offset().top );
+ //gridViewport.
+
+ };
+ $(window).resize(resizer);
+
+ // get saved tree state
+ $scope.ActivatedKey = loadActivatedNode();
+ let expandedNodeList = loadExpandedNodeList();
+
+ var showCharts = function () {
+
+ };
+ let charts = new QDR.Folder('Charts');
+ charts.info = {fn: showCharts};
+ charts.type = 'Charts'; // for the charts template
+ charts.key = 'Charts';
+ charts.extraClasses = 'charts';
+ topLevelChildren.push(charts);
+
+ // create a routers tree branch
+ let routers = new QDR.Folder('Routers');
+ routers.type = 'Routers';
+ routers.info = {fn: allRouterInfo};
+ routers.expanded = (expandedNodeList.indexOf('Routers') > -1);
+ routers.key = 'Routers';
+ routers.parentKey= 'Routers';
+ routers.extraClasses = 'routers';
+ topLevelChildren.push(routers);
+ // called when the list of routers changes
+ var updateRouterTree = function (nodes) {
+ var worker = function (node) {
+ let name = QDRService.management.topology.nameFromId(node);
+ let router = new QDR.Leaf(name);
+ router.type = 'Router';
+ router.info = {fn: routerInfo};
+ router.nodeId = node;
+ router.key = node;
+ router.extraClasses = 'router';
+ router.parentKey = 'Routers';
+ return router;
+ };
+ updateLeaves(nodes, 'Routers', worker);
+ };
+
+ // create an addresses tree branch
+ let addresses = new QDR.Folder('Addresses');
+ addresses.type = 'Addresses';
+ addresses.info = {fn: allAddressInfo};
+ addresses.expanded = (expandedNodeList.indexOf('Addresses') > -1);
+ addresses.key = 'Addresses';
+ addresses.parentKey = 'Addresses';
+ addresses.extraClasses = 'addresses';
+ topLevelChildren.push(addresses);
+ var updateAddressTree = function (addressFields) {
+ var worker = function (address) {
+ let a = new QDR.Leaf(address.title);
+ a.info = {fn: addressInfo};
+ a.key = address.uid;
+ a.fields = address;
+ a.type = 'Address';
+ a.tooltip = address['class'] + ' address';
+ if (address.address === '$management')
+ a.tooltip = 'internal ' + a.tooltip;
+ a.extraClasses = a.tooltip;
+ a.parentKey = 'Addresses';
+ return a;
+ };
+ updateLeaves(addressFields, 'Addresses', worker);
+ };
+
+ $scope.$watch('filter', function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ allLinkInfo(null, function () {$timeout(function (){});});
+ localStorage[FILTERKEY] = JSON.stringify($scope.filter);
+ }
+ }, true);
+
+ $scope.filterToggle = function () {
+ let filter = $('#linkFilter');
+ filter.toggle();
+ };
+
+ $scope.filter = angular.fromJson(localStorage[FILTERKEY]) || {endpointsOnly: 'true', hideConsoles: true};
+ let links = new QDR.Folder('Links');
+ links.type = 'Links';
+ links.info = {fn: allLinkInfo};
+ links.expanded = (expandedNodeList.indexOf('Links') > -1);
+ links.key = 'Links';
+ links.parentKey = 'Links';
+ links.extraClasses = 'links';
+ topLevelChildren.push(links);
+
+ // called both before the tree is created and whenever a background update is done
+ var updateLinkTree = function (linkFields) {
+ var worker = function (link) {
+ let l = new QDR.Leaf(link.title);
+ let isConsole = QDRService.utilities.isConsole(QDRService.management.topology.getConnForLink(link));
+ l.info = {fn: linkInfo};
+ l.key = link.uid;
+ l.fields = link;
+ l.type = 'Link';
+ l.parentKey = 'Links';
+ if (isConsole)
+ l.tooltip = 'console link';
+ else
+ l.tooltip = link.linkType + ' link';
+ l.extraClasses = l.tooltip;
+ return l;
+ };
+ updateLeaves(linkFields, 'Links', worker);
+ };
+
+ let connections = new QDR.Folder('Connections');
+ connections.type = 'Connections';
+ connections.info = {fn: allConnectionInfo};
+ connections.expanded = (expandedNodeList.indexOf('Connections') > -1);
+ connections.key = 'Connections';
+ connections.parentKey = 'Connections';
+ connections.extraClasses = 'connections';
+ topLevelChildren.push(connections);
+
+ var updateConnectionTree = function (connectionFields) {
+ var worker = function (connection) {
+ let host = connection.host;
+ if (connection.name === 'connection/' && connection.role === 'inter-router' && connection.host === '')
+ host = connection.container + ':' + connection.identity;
+
+ let c = new QDR.Leaf(host);
+ let isConsole = QDRService.utilities.isAConsole (connection.properties, connection.identity, connection.role, connection.routerId);
+ c.type = 'Connection';
+ c.info = {fn: connectionInfo};
+ c.key = connection.uid;
+ c.fields = connection;
+ if (isConsole)
+ c.tooltip = 'console connection';
+ else
+ c.tooltip = connection.role === 'inter-router' ? 'inter-router connection' : 'external connection';
+ c.extraClasses = c.tooltip;
+ c.parentKey = 'Connections';
+ return c;
+ };
+ updateLeaves(connectionFields, 'Connections', worker);
+ };
+
+ var updateLogTree = function (logFields) {
+ var worker = function (log) {
+ let l = new QDR.Leaf(log.name);
+ l.type = 'Log';
+ l.info = {fn: logInfo};
+ l.key = log.name;
+ l.parentKey = 'Logs';
+ l.extraClasses = 'log';
+ return l;
+ };
+ updateLeaves(logFields, 'Logs', worker);
+ };
+
+ let htmlReady = false;
+ let dataReady = false;
+ $scope.largeNetwork = QDRService.management.topology.isLargeNetwork();
+ let logs = new QDR.Folder('Logs');
+ logs.type = 'Logs';
+ logs.info = {fn: allLogInfo};
+ logs.expanded = (expandedNodeList.indexOf('Logs') > -1);
+ logs.key = 'Logs';
+ logs.parentKey = 'Logs';
+ if (QDRService.management.connection.versionCheck('0.8.0'))
+ topLevelChildren.push(logs);
+
+ var onTreeInitialized = function (event, data) {
+ treeRoot = data.tree;
+ let activeNode = treeRoot.getNodeByKey($scope.ActivatedKey);
+ if (activeNode)
+ activeNode.setActive(true);
+ updateExpanded();
+ };
+ var initTreeAndGrid = function () {
+ if (!htmlReady || !dataReady)
+ return;
+ let div = angular.element('#overtree');
+ if (!div.width()) {
+ setTimeout(initTreeAndGrid, 100);
+ return;
+ }
+ $('#overtree').fancytree({
+ activate: onTreeNodeActivated,
+ expand: onTreeNodeExpanded,
+ init: onTreeInitialized,
+ autoCollapse: $scope.largeNetwork,
+ activeVisible:! $scope.largeNetwork,
+ clickFolderMode: 1,
+ source: topLevelChildren
+ });
+ };
+
+ $scope.overviewLoaded = function () {
+ htmlReady = true;
+ initTreeAndGrid();
+ };
+
+ let nodeIds = QDRService.management.topology.nodeIdList();
+ // add placeholders for the top level tree nodes
+ let topLevelTreeNodes = [routers, addresses, links, connections, logs];
+ topLevelTreeNodes.forEach( function (parent) {
+ let placeHolder = new QDR.Leaf('loading...');
+ placeHolder.extraClasses = 'loading';
+ parent.children = [placeHolder];
+ });
+
+ var updateExpanded = function () {
+ QDR.log.debug('updateExpandedEntities');
+ clearTimeout(updateIntervalHandle);
+
+ let tree = $('#overtree').fancytree('getTree');
+ if (tree && tree.visit) {
+ let q = d3.queue(10);
+ tree.visit( function (node) {
+ if (node.isActive() || node.isExpanded()) {
+ q.defer(node.data.info.fn, node);
+ }
+ });
+ q.await( function (error) {
+ if (error)
+ QDR.log.error(error.message);
+
+ // if there are no active nodes, activate the saved one
+ let tree = $('#overtree').fancytree('getTree');
+ if (!tree.getActiveNode()) {
+ let active = tree.getNodeByKey($scope.ActivatedKey);
+ if (active) {
+ active.setActive(true);
+ } else {
+ tree.getNodeByKey('Routers').setActive(true);
+ }
+ }
+
+ // fancytree animations sometimes stop short of shrinking its placeholder spans
+ d3.selectAll('.ui-effects-placeholder').style('height', '0px');
+ // once all expanded tree nodes have been update, schedule another update
+ $timeout(function () {
+ updateIntervalHandle = setTimeout(updateExpanded, updateInterval);
+ });
+ });
+ }
+ };
+
+ dataReady = true;
+ initTreeAndGrid();
+ $scope.$on('$destroy', function() {
+ clearTimeout(updateIntervalHandle);
+ $(window).off('resize', resizer);
+ });
+ }]);
+
+ return QDR;
+
+}(QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrOverviewChartsController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverviewChartsController.js b/console/stand-alone/plugin/js/qdrOverviewChartsController.js
new file mode 100644
index 0000000..e47e597
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverviewChartsController.js
@@ -0,0 +1,134 @@
+/*
+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.
+*/
+'use strict';
+/* global angular */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ QDR.module.controller('QDR.OverviewChartsController', function ($scope, QDRService, QDRChartService, $timeout) {
+
+ $scope.overviewCharts = [];
+ let updateTimer;
+
+ // called when it is time to update the chart's data
+ var sum = function (request, saveResponse) {
+ let attrs = angular.copy(request.overrideAttrs);
+ let totalAttr = attrs.shift(); // remove totals attr
+ let attrEntities = [{entity: request.entity, attrs: attrs}];
+ QDRService.management.topology.fetchAllEntities(attrEntities, function (responses) {
+ let total = 0;
+ let response = {attributeNames: [totalAttr, 'name'], results: [[]]};
+ // for each router
+ for (let router in responses) {
+ let record = responses[router][request.entity];
+ let accessor = charts.find( function (chart) { return chart.nodeId === request.nodeId;}).accessor;
+ // for each attribute-value (ie each address or each link)
+ for (let i=0; i<record.results.length; i++) {
+ total += accessor(record.attributeNames, record.results[i]);
+ }
+ }
+ response.results[0][0] = total;
+ response.results[0][1] = request.names()[0];
+ saveResponse(request.nodeId, request.entity, response);
+ });
+ };
+ let charts = [
+
+ {
+ nodeId: '///Throughput/',
+ entity: 'router.address',
+ name: 'throughput',
+ overrideAttrs: ['throughput', 'deliveriesEgress'],
+ attr: 'throughput',
+ type: 'rate',
+ hideLabel: true,
+ hideLegend: true,
+ rateWindow: 5000, // update data once every 5 seconds
+ visibleDuration: 1, // show data for the last 1 minute
+ forceCreate: true,
+ accessor: function (attributes, results) {
+ return results[attributes.indexOf('deliveriesEgress')];
+ },
+ override: sum // called to fetch the chart data
+ },
+ {
+ nodeId: '///Outstanding-Deliveries/',
+ entity: 'router.link',
+ name: 'outstandingDeliveries',
+ overrideAttrs:['outstandingDeliveries', 'undeliveredCount', 'unsettledCount', 'linkType', 'linkDir'],
+ attr: 'outstandingDeliveries',
+ hideLabel: true,
+ hideLegend: true,
+ visibleDuration: 1, // show data for the last 1 minute
+ forceCreate: true,
+ accessor: function (attributes, results) {
+ return results[attributes.indexOf('linkType')] === 'endpoint' && results[attributes.indexOf('linkDir')] === 'out'
+ ? results[attributes.indexOf('unsettledCount')] + results[attributes.indexOf('undeliveredCount')]
+ : 0;
+ },
+ now: new Date(),
+ override: sum // called to fetch the chart data
+ }
+ ];
+ $scope.overviewCharts = charts.map( function (chart) {
+ let c = QDRChartService.registerChart(chart);
+ return new QDRChartService.pfAreaChart(c, c.id(), true);
+ });
+
+
+ // redraw the chart every update period
+ var updateCharts = function () {
+ $scope.overviewCharts.forEach(function (svgChart) {
+ svgChart.tick(svgChart.chart.id()); // on this page we are using the chart.id() as the div id in which to render the chart
+ });
+ };
+
+ var createCharts = function () {
+ // ensure the div for our chart is loaded in the dom
+ let div = angular.element('.chartContainer');
+ if (!div.width()) {
+ setTimeout(createCharts, 100);
+ return;
+ }
+ // create an svg object for each chart
+ $scope.overviewCharts.forEach ( function (c) {
+ // tell c3 to create the svg
+ c.generate(360);
+ });
+ // redraw the charts once every second
+ const updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 1000;
+ updateTimer = setInterval(updateCharts, updateRate);
+ };
+ $timeout( function () {
+ createCharts();
+ });
+
+ $scope.$on('$destroy', function() {
+ if (updateTimer)
+ clearInterval(updateTimer);
+ $scope.overviewCharts.forEach( function (svg) {
+ QDRChartService.unRegisterChart(svg.chart);
+ });
+ });
+ });
+ return QDR;
+
+} (QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrOverviewLogsController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverviewLogsController.js b/console/stand-alone/plugin/js/qdrOverviewLogsController.js
new file mode 100644
index 0000000..1e541a5
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrOverviewLogsController.js
@@ -0,0 +1,70 @@
+/*
+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.
+*/
+'use strict';
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ QDR.module.controller('QDR.OverviewLogsController', function ($scope, $uibModalInstance, QDRService, $timeout, nodeName, nodeId, module, level) {
+
+ var gotLogInfo = function (nodeId, response, context) {
+ let statusCode = context.message.application_properties.statusCode;
+ if (statusCode < 200 || statusCode >= 300) {
+ QDR.Core.notification('error', context.message.statusDescription);
+ QDR.log.info('Error ' + context.message.statusDescription);
+ } else {
+ let levelLogs = response.filter( function (result) {
+ if (result[1] == null)
+ result[1] = 'error';
+ return result[1].toUpperCase() === level.toUpperCase() && result[0] === module;
+ });
+ let logFields = levelLogs.map( function (result) {
+ return {
+ nodeId: QDRService.management.topology.nameFromId(nodeId),
+ name: result[0],
+ type: result[1],
+ message: result[2],
+ source: result[3],
+ line: result[4],
+ time: Date(result[5]).toString()
+ };
+ });
+ $timeout(function () {
+ $scope.loading = false;
+ $scope.logFields = logFields;
+ });
+ }
+ };
+ QDRService.management.connection.sendMethod(nodeId, undefined, {}, 'GET-LOG', {module: module})
+ .then( function (response) {gotLogInfo(nodeId, response.response, response.context);});
+
+ $scope.loading = true;
+ $scope.module = module;
+ $scope.level = level;
+ $scope.nodeName = nodeName;
+ $scope.logFields = [];
+ $scope.ok = function () {
+ $uibModalInstance.close(true);
+ };
+
+ });
+ return QDR;
+
+} (QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrSchema.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrSchema.js b/console/stand-alone/plugin/js/qdrSchema.js
new file mode 100644
index 0000000..69569ea
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrSchema.js
@@ -0,0 +1,83 @@
+/*
+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.
+*/
+'use strict';
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+ QDR.module.controller('QDR.SchemaController', ['$scope', '$location', '$timeout', 'QDRService', function($scope, $location, $timeout, QDRService) {
+ if (!QDRService.management.connection.is_connected()) {
+ QDR.redirectWhenConnected($location, 'schema');
+ return;
+ }
+ var onDisconnect = function () {
+ $timeout( function () {QDR.redirectWhenConnected('schema');});
+ };
+ // we are currently connected. setup a handler to get notified if we are ever disconnected
+ QDRService.management.connection.addDisconnectAction( onDisconnect );
+
+ var keys2kids = function (tree, obj) {
+ if (obj === Object(obj)) {
+ tree.children = [];
+ let keys = Object.keys(obj).sort();
+ for (let i=0; i<keys.length; ++i) {
+ let key = keys[i];
+ let kid = {title: key};
+ if (obj[key] === Object(obj[key])) {
+ kid.folder = true;
+ keys2kids(kid, obj[key]);
+ } else {
+ kid.title += (': ' + JSON.stringify(obj[key],null,2));
+ }
+ tree.children.push(kid);
+ }
+ }
+ };
+
+ let tree = [];
+ for (let key in QDRService.management.schema()) {
+ let kid = {title: key};
+ kid.folder = true;
+ let val = QDRService.management.schema()[key];
+ if (val === Object(val))
+ keys2kids(kid, val);
+ else
+ kid.title += (': ' + JSON.stringify(val,null,2));
+
+ tree.push(kid);
+ }
+ $('#schema').fancytree({
+ minExpandLevel: 2,
+ clickFolderMode: 3,
+ classNames: {
+ expander: 'fa-angle',
+ connector: 'fancytree-no-connector'
+ },
+ source: tree
+ });
+
+ $scope.$on('$destroy', function() {
+ QDRService.management.connection.delDisconnectAction( onDisconnect );
+ });
+
+ }]);
+
+ return QDR;
+}(QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrService.js b/console/stand-alone/plugin/js/qdrService.js
new file mode 100644
index 0000000..f4effdd
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrService.js
@@ -0,0 +1,269 @@
+/*
+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.
+*/
+'use strict';
+/* global Promise */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ // The QDR service handles the connection to the router
+ QDR.module.factory('QDRService', ['$timeout', '$location', function($timeout, $location) {
+ let dm = require('dispatch-management');
+ let self = {
+ management: new dm.Management($location.protocol()),
+ utilities: dm.Utilities,
+
+ onReconnect: function () {
+ self.management.connection.on('disconnected', self.onDisconnect);
+ let org = localStorage[QDR.LAST_LOCATION] || '/overview';
+ $timeout ( function () {
+ $location.path(org);
+ $location.search('org', null);
+ $location.replace();
+ });
+ },
+ onDisconnect: function () {
+ self.management.connection.on('connected', self.onReconnect);
+ $timeout( function () {
+ $location.path('/connect');
+ let curPath = $location.path();
+ let parts = curPath.split('/');
+ let org = parts[parts.length-1];
+ if (org && org.length > 0 && org !== 'connect') {
+ $location.search('org', org);
+ } else {
+ $location.search('org', null);
+ }
+ $location.replace();
+ });
+ },
+
+ connect: function (connectOptions) {
+ return new Promise ( function (resolve, reject) {
+ self.management.connection.connect(connectOptions)
+ .then( function (r) {
+ // if we are ever disconnected, show the connect page and wait for a reconnect
+ self.management.connection.on('disconnected', self.onDisconnect);
+
+ self.management.getSchema()
+ .then( function () {
+ QDR.log.info('got schema after connection');
+ self.management.topology.setUpdateEntities([]);
+ QDR.log.info('requesting a topology');
+ self.management.topology.get() // gets the list of routers
+ .then( function () {
+ QDR.log.info('got initial topology');
+ let curPath = $location.path();
+ let parts = curPath.split('/');
+ let org = parts[parts.length-1];
+ if (org === '' || org === 'connect') {
+ org = localStorage[QDR.LAST_LOCATION] || QDR.pluginRoot + '/overview';
+ }
+ $timeout ( function () {
+ $location.path(org);
+ $location.search('org', null);
+ $location.replace();
+ });
+ });
+ });
+ resolve(r);
+ }, function (e) {
+ reject(e);
+ });
+ });
+ },
+ disconnect: function () {
+ self.management.connection.disconnect();
+ delete self.management;
+ self.management = new dm.Management($location.protocol());
+ }
+
+
+ };
+
+ return self;
+ }]);
+
+ return QDR;
+
+}(QDR || {}));
+
+(function() {
+ console.dump = function(o) {
+ if (window.JSON && window.JSON.stringify)
+ QDR.log.info(JSON.stringify(o, undefined, 2));
+ else
+ console.log(o);
+ };
+})();
+
+if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function (searchString, position) {
+ return this.substr(position || 0, searchString.length) === searchString;
+ };
+}
+
+if (!String.prototype.endsWith) {
+ String.prototype.endsWith = function(searchString, position) {
+ let subjectString = this.toString();
+ if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
+ position = subjectString.length;
+ }
+ position -= searchString.length;
+ let lastIndex = subjectString.lastIndexOf(searchString, position);
+ return lastIndex !== -1 && lastIndex === position;
+ };
+}
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
+if (!Array.prototype.findIndex) {
+ Object.defineProperty(Array.prototype, 'findIndex', {
+ value: function(predicate) {
+ // 1. Let O be ? ToObject(this value).
+ if (this == null) {
+ throw new TypeError('"this" is null or not defined');
+ }
+
+ let o = Object(this);
+
+ // 2. Let len be ? ToLength(? Get(O, "length")).
+ let len = o.length >>> 0;
+
+ // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+ if (typeof predicate !== 'function') {
+ throw new TypeError('predicate must be a function');
+ }
+
+ // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ let thisArg = arguments[1];
+
+ // 5. Let k be 0.
+ let k = 0;
+
+ // 6. Repeat, while k < len
+ while (k < len) {
+ // a. Let Pk be ! ToString(k).
+ // b. Let kValue be ? Get(O, Pk).
+ // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
+ // d. If testResult is true, return k.
+ let kValue = o[k];
+ if (predicate.call(thisArg, kValue, k, o)) {
+ return k;
+ }
+ // e. Increase k by 1.
+ k++;
+ }
+
+ // 7. Return -1.
+ return -1;
+ }
+ });
+}
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.find
+if (!Array.prototype.find) {
+ Object.defineProperty(Array.prototype, 'find', {
+ value: function(predicate) {
+ // 1. Let O be ? ToObject(this value).
+ if (this == null) {
+ throw new TypeError('"this" is null or not defined');
+ }
+
+ let o = Object(this);
+
+ // 2. Let len be ? ToLength(? Get(O, "length")).
+ let len = o.length >>> 0;
+
+ // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+ if (typeof predicate !== 'function') {
+ throw new TypeError('predicate must be a function');
+ }
+
+ // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ let thisArg = arguments[1];
+
+ // 5. Let k be 0.
+ let k = 0;
+
+ // 6. Repeat, while k < len
+ while (k < len) {
+ // a. Let Pk be ! ToString(k).
+ // b. Let kValue be ? Get(O, Pk).
+ // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
+ // d. If testResult is true, return kValue.
+ let kValue = o[k];
+ if (predicate.call(thisArg, kValue, k, o)) {
+ return kValue;
+ }
+ // e. Increase k by 1.
+ k++;
+ }
+
+ // 7. Return undefined.
+ return undefined;
+ }
+ });
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
+if (!Array.prototype.fill) {
+ Object.defineProperty(Array.prototype, 'fill', {
+ value: function(value) {
+
+ // Steps 1-2.
+ if (this == null) {
+ throw new TypeError('this is null or not defined');
+ }
+
+ var O = Object(this);
+
+ // Steps 3-5.
+ var len = O.length >>> 0;
+
+ // Steps 6-7.
+ var start = arguments[1];
+ var relativeStart = start >> 0;
+
+ // Step 8.
+ var k = relativeStart < 0 ?
+ Math.max(len + relativeStart, 0) :
+ Math.min(relativeStart, len);
+
+ // Steps 9-10.
+ var end = arguments[2];
+ var relativeEnd = end === undefined ?
+ len : end >> 0;
+
+ // Step 11.
+ var final = relativeEnd < 0 ?
+ Math.max(len + relativeEnd, 0) :
+ Math.min(relativeEnd, len);
+
+ // Step 12.
+ while (k < final) {
+ O[k] = value;
+ k++;
+ }
+
+ // Step 13.
+ return O;
+ }
+ });
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[2/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js
files that were in the npm repository
Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrSettings.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrSettings.js b/console/stand-alone/plugin/js/qdrSettings.js
new file mode 100644
index 0000000..7f34758
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrSettings.js
@@ -0,0 +1,172 @@
+/*
+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.
+*/
+'use strict';
+/* global angular */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ /**
+ * @method SettingsController
+ * @param $scope
+ * @param QDRServer
+ *
+ * Controller that handles the QDR settings page
+ */
+
+ QDR.module.controller('QDR.SettingsController', ['$scope', 'QDRService', 'QDRChartService', '$timeout', function($scope, QDRService, QDRChartService, $timeout) {
+
+ $scope.connecting = false;
+ $scope.connectionError = false;
+ $scope.connectionErrorText = undefined;
+ $scope.forms = {};
+
+ $scope.formEntity = angular.fromJson(localStorage[QDR.SETTINGS_KEY]) || {
+ address: '',
+ port: '',
+ username: '',
+ password: '',
+ autostart: false
+ };
+
+ $scope.$watch('formEntity', function(newValue, oldValue) {
+ if (newValue !== oldValue) {
+ localStorage[QDR.SETTINGS_KEY] = angular.toJson(newValue);
+ }
+ }, true);
+
+ $scope.buttonText = function() {
+ if (QDRService.management.connection.is_connected()) {
+ return 'Disconnect';
+ } else {
+ return 'Connect';
+ }
+ };
+
+ // connect/disconnect button clicked
+ $scope.connect = function() {
+ if (QDRService.management.connection.is_connected()) {
+ $timeout( function () {
+ QDRService.disconnect();
+ });
+ return;
+ }
+
+ if ($scope.settings.$valid) {
+ $scope.connectionError = false;
+ $scope.connecting = true;
+ // timeout so connecting animation can display
+ $timeout(function () {
+ doConnect();
+ });
+ }
+ };
+
+ var doConnect = function() {
+ QDR.log.info('doConnect called on connect page');
+ if (!$scope.formEntity.address)
+ $scope.formEntity.address = 'localhost';
+ if (!$scope.formEntity.port)
+ $scope.formEntity.port = 5673;
+
+ var failed = function() {
+ $timeout(function() {
+ $scope.connecting = false;
+ $scope.connectionErrorText = 'Unable to connect to ' + $scope.formEntity.address + ':' + $scope.formEntity.port;
+ $scope.connectionError = true;
+ });
+ };
+ let options = {address: $scope.formEntity.address, port: $scope.formEntity.port, reconnect: true};
+ QDRService.connect(options)
+ .then( function () {
+ // register a callback for when the node list is available (needed for loading saved charts)
+ QDRService.management.topology.addUpdatedAction('initChartService', function() {
+ QDRService.management.topology.delUpdatedAction('initChartService');
+ QDRChartService.init(); // initialize charting service after we are connected
+ });
+ // get the list of nodes
+ QDRService.management.topology.startUpdating(false);
+ // will have redirected to last known page or /overview
+ }, function (e) {
+ failed(e);
+ });
+ };
+ }]);
+
+
+ QDR.module.directive('posint', function() {
+ return {
+ require: 'ngModel',
+
+ link: function(scope, elem, attr, ctrl) {
+ // input type number allows + and - but we don't want them so filter them out
+ elem.bind('keypress', function(event) {
+ let nkey = !event.charCode ? event.which : event.charCode;
+ let skey = String.fromCharCode(nkey);
+ let nono = '-+.,';
+ if (nono.indexOf(skey) >= 0) {
+ event.preventDefault();
+ return false;
+ }
+ // firefox doesn't filter out non-numeric input. it just sets the ctrl to invalid
+ if (/[!@#$%^&*()]/.test(skey) && event.shiftKey || // prevent shift numbers
+ !( // prevent all but the following
+ nkey <= 0 || // arrows
+ nkey == 8 || // delete|backspace
+ nkey == 13 || // enter
+ (nkey >= 37 && nkey <= 40) || // arrows
+ event.ctrlKey || event.altKey || // ctrl-v, etc.
+ /[0-9]/.test(skey)) // numbers
+ ) {
+ event.preventDefault();
+ return false;
+ }
+ });
+ // check the current value of input
+ var _isPortInvalid = function(value) {
+ let port = value + '';
+ let isErrRange = false;
+ // empty string is valid
+ if (port.length !== 0) {
+ let n = ~~Number(port);
+ if (n < 1 || n > 65535) {
+ isErrRange = true;
+ }
+ }
+ ctrl.$setValidity('range', !isErrRange);
+ return isErrRange;
+ };
+
+ //For DOM -> model validation
+ ctrl.$parsers.unshift(function(value) {
+ return _isPortInvalid(value) ? undefined : value;
+ });
+
+ //For model -> DOM validation
+ ctrl.$formatters.unshift(function(value) {
+ _isPortInvalid(value);
+ return value;
+ });
+ }
+ };
+ });
+
+ return QDR;
+}(QDR || {}));
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/qdrTopAddressesController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrTopAddressesController.js b/console/stand-alone/plugin/js/qdrTopAddressesController.js
new file mode 100644
index 0000000..ece11ef
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrTopAddressesController.js
@@ -0,0 +1,218 @@
+/*
+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.
+*/
+'use strict';
+/* global angular */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+ QDR.module.controller('QDR.TopAddressesController', function ($scope, QDRService, $timeout) {
+
+ $scope.addressesData = [];
+ $scope.topAddressesGrid = {
+ data: 'addressesData',
+ columnDefs: [
+ {
+ field: 'address',
+ displayName: 'address'
+ },
+ {
+ field: 'class',
+ displayName: 'class'
+ },
+ /*
+ {
+ field: 'phase',
+ displayName: 'phase',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'inproc',
+ displayName: 'in-proc'
+ },
+ {
+ field: 'local',
+ displayName: 'local',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'remote',
+ displayName: 'remote',
+ cellClass: 'grid-align-value'
+ },
+ */
+ {
+ field: 'in',
+ displayName: 'in',
+ cellClass: 'grid-align-value'
+ },
+ {
+ field: 'out',
+ displayName: 'out',
+ cellClass: 'grid-align-value'
+ }
+ ],
+ enableColumnResize: true,
+ enableHorizontalScrollbar: 0,
+ enableVerticalScrollbar: 0,
+ multiSelect: false,
+ enableSelectAll: false,
+ enableSelectionBatchEvent: false,
+ enableRowHeaderSelection: false,
+ noUnselect: true
+ };
+
+ // get info for all addresses
+ var allAddressInfo = function (address, callback) {
+ let nodes = {};
+ // gets called each node/entity response
+ var gotNode = function (nodeName, entity, response) {
+ if (!nodes[nodeName])
+ nodes[nodeName] = {};
+ nodes[nodeName][entity] = angular.copy(response);
+ };
+ var addr_phase = function (addr) {
+ if (!addr)
+ return '-';
+ if (addr[0] == 'M')
+ return addr[1];
+ return '';
+ };
+ var prettyVal = function (val) {
+ return QDRService.utilities.pretty(val || '-');
+ };
+ let addressFields = [];
+ let addressObjs = {};
+ let addNull = function (oldVal, newVal) {
+ if (oldVal != null && newVal != null)
+ return oldVal + newVal;
+ if (oldVal != null)
+ return oldVal;
+ return newVal;
+ };
+ // send the requests for all connection and router info for all routers
+ QDRService.management.topology.fetchAllEntities({entity: 'router.address'}, function () {
+ for (let node in nodes) {
+ let response = nodes[node]['router.address'];
+ response.results.forEach( function (result) {
+ let address = QDRService.utilities.flatten(response.attributeNames, result);
+ let uid = address.identity;
+ let identity = QDRService.utilities.identity_clean(uid);
+
+ let objname = QDRService.utilities.addr_text(identity)+QDRService.utilities.addr_class(identity);
+ if (!addressObjs[objname]) {
+ addressObjs[objname] = {
+ address: QDRService.utilities.addr_text(identity),
+ 'class': QDRService.utilities.addr_class(identity),
+ phase: addr_phase(identity),
+ inproc: address.inProcess,
+ local: address.subscriberCount,
+ remote: address.remoteCount,
+ 'in': address.deliveriesIngress,
+ out: address.deliveriesEgress,
+ thru: address.deliveriesTransit,
+ toproc: address.deliveriesToContainer,
+ fromproc:address.deliveriesFromContainer,
+ uid: uid
+ };
+ }
+ else {
+ let sumObj = addressObjs[objname];
+ sumObj.inproc = addNull(sumObj.inproc, address.inProcess);
+ sumObj.local = addNull(sumObj.local, address.subscriberCount);
+ sumObj.remote = addNull(sumObj.remote, address.remoteCount);
+ sumObj['in'] = addNull(sumObj['in'], address.deliveriesIngress);
+ sumObj.out = addNull(sumObj.out, address.deliveriesEgress);
+ sumObj.thru = addNull(sumObj.thru, address.deliveriesTransit);
+ sumObj.toproc = addNull(sumObj.toproc, address.deliveriesToContainer);
+ sumObj.fromproc = addNull(sumObj.fromproc, address.deliveriesFromContainer);
+ }
+ });
+ }
+ for (let obj in addressObjs) {
+ addressObjs[obj].inproc = prettyVal(addressObjs[obj].inproc);
+ addressObjs[obj].local = prettyVal(addressObjs[obj].local);
+ addressObjs[obj].remote = prettyVal(addressObjs[obj].remote);
+ addressObjs[obj]['in'] = prettyVal(addressObjs[obj]['in']);
+ addressObjs[obj].rawout = addressObjs[obj].out;
+ addressObjs[obj].out = prettyVal(addressObjs[obj].out);
+ addressObjs[obj].thru = prettyVal(addressObjs[obj].thru);
+ addressObjs[obj].toproc = prettyVal(addressObjs[obj].toproc);
+ addressObjs[obj].fromproc = prettyVal(addressObjs[obj].fromproc);
+ addressFields.push(addressObjs[obj]);
+ }
+ if (addressFields.length === 0)
+ return;
+ // update the grid's data
+ addressFields.sort ( function (a,b) {
+ return a.address + a['class'] < b.address + b['class'] ? -1 : a.address + a['class'] > b.address + b['class'] ? 1 : 0;}
+ );
+ // callapse all records with same addres+class
+ addressFields[0].title = addressFields[0].address;
+ for (let i=1; i<addressFields.length; ++i) {
+ // if this address is the same as the previous address, add a class to the display titles
+ if (addressFields[i].address === addressFields[i-1].address) {
+ addressFields[i-1].title = addressFields[i-1].address + ' (' + addressFields[i-1]['class'] + ')';
+ addressFields[i].title = addressFields[i].address + ' (' + addressFields[i]['class'] + ')';
+ } else
+ addressFields[i].title = addressFields[i].address;
+ }
+ addressFields = addressFields.filter( function (address) {
+ return address.rawout > 0 || address.rawin > 0;
+ });
+ addressFields.sort ( function (a,b) {
+ return a.rawout < b.rawout ? -1 : a.rawout > b.rawout ? 1 : 0;}
+ );
+ // take top 5 records
+ addressFields.splice(5);
+
+ $scope.addressesData = addressFields;
+ callback(null);
+ }, gotNode);
+ };
+ let timer;
+ var updateGrid = function () {
+ $timeout( function () {
+ allAddressInfo(null, function () {});
+ expandGridToContent($scope.addressesData.length);
+ });
+ };
+ timer = setInterval(updateGrid, 5000);
+ updateGrid();
+
+ var expandGridToContent = function (rows) {
+ let height = (rows+1) * 30 + 40; // header is 40px
+ let gridDetails = $('#overview-controller .grid');
+ gridDetails.css('height', height + 'px');
+ };
+
+ $scope.anyAddresses = function () {
+ return $scope.addressesData.length > 0;
+ };
+
+ $scope.$on('$destroy', function() {
+ if (timer)
+ clearInterval(timer);
+ });
+
+ });
+ return QDR;
+
+} (QDR || {}));
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org