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 2016/10/19 12:49:00 UTC

[01/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master 979030344 -> 0c58c3814


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.node-controller.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.node-controller.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.node-controller.js
new file mode 100644
index 0000000..2f31cca
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.node-controller.js
@@ -0,0 +1,294 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+  'use strict';
+
+  angular
+    .module('horizon.dashboard.dispatch.topology')
+    .controller('horizon.dashboard.dispatch.topology.TopologyNodeController', TopologyNodeController);
+
+  TopologyNodeController.$inject = [
+    '$scope',
+    'horizon.dashboard.dispatch.comService'
+  ]
+
+  function TopologyNodeController($scope, QDRService, dialog, newname) {
+   		var schema = QDRService.schema;
+   		var myEntities = ['router', 'log', 'listener' ];
+   		var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean'};
+		var newLinks = $('path.temp').toArray();    // jquery array of new links for the added router
+		var nodeInfo = QDRService.topology.nodeInfo();
+		var separatedEntities = []; // additional entities required if a link is reversed
+		var myPort = 0, myAddr = '0.0.0.0'; // port and address for new router
+   		$scope.entities = [];
+
+		// find max port number that is used in all the listeners
+		var getMaxPort = function (nodeInfo) {
+			var maxPort = 5674;
+			for (var key in nodeInfo) {
+				var node = nodeInfo[key];
+				var listeners = node['.listener'];
+				var attrs = listeners.attributeNames;
+				for (var i=0; i<listeners.results.length; ++i) {
+					var res = listeners.results[i];
+					var port = QDRService.valFor(attrs, res, 'port');
+					if (parseInt(port, 10) > maxPort)
+						maxPort = parseInt(port, 10);
+				}
+			}
+			return maxPort;
+		}
+		var maxPort = getMaxPort(nodeInfo);
+
+		// construct an object that contains all the info needed for a single tab's fields
+		var entity = function (actualName, tabName, humanName, ent, icon, link) {
+			var nameIndex = -1; // the index into attributes that the name field was placed
+			var index = 0;
+			var info = {
+			    actualName: actualName,
+				tabName:    tabName,
+				humanName:  humanName,
+				description:ent.description,
+				icon:       angular.isDefined(icon) ? icon : '',
+				references: ent.references,
+				link:       link,
+
+   		        attributes: $.map(ent.attributes, function (value, key) {
+					// skip identity and depricated fields
+   		            if (key == 'identity' || value.description.startsWith('Deprecated'))
+   		                return null;
+					var val = value['default'];
+					if (key == 'name')
+						nameIndex = index;
+					index++;
+					return {    name:       key,
+								humanName:  QDRService.humanify(key),
+                                description:value.description,
+                                type:       typeMap[value.type],
+                                rawtype:    value.type,
+                                input:      typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input'
+                                                                          : 'select',
+                                selected:   val ? val : undefined,
+                                'default':  value['default'],
+                                value:      val,
+                                required:   value.required,
+                                unique:     value.unique
+                    };
+                })
+			}
+			// move the 'name' attribute to the 1st position
+			if (nameIndex > -1) {
+				var tmp = info.attributes[0];
+				info.attributes[0] = info.attributes[nameIndex];
+				info.attributes[nameIndex] = tmp;
+			}
+			return info;
+		}
+
+		// remove the annotation fields
+		var stripAnnotations = function (entityName, ent, annotations) {
+			if (ent.references) {
+				var newEnt = {attributes: {}};
+				ent.references.forEach( function (annoKey) {
+					if (!annotations[annoKey])
+						annotations[annoKey] = {};
+					annotations[annoKey][entityName] = true;    // create the key/consolidate duplicates
+					var keys = Object.keys(schema.annotations[annoKey].attributes);
+					for (var attrib in ent.attributes) {
+						if (keys.indexOf(attrib) == -1) {
+							newEnt.attributes[attrib] = ent.attributes[attrib];
+						}
+					}
+					// add a field for the reference name
+					newEnt.attributes[annoKey] = {type: 'string',
+							description: 'Name of the ' + annoKey + ' section.',
+							'default': annoKey, required: true};
+				})
+				newEnt.references = ent.references;
+				newEnt.description = ent.description;
+				return newEnt;
+			}
+			return ent;
+		}
+
+		var annotations = {};
+   		myEntities.forEach(function (entityName) {
+   		    var ent = schema.entityTypes[entityName];
+   		    var hName = QDRService.humanify(entityName);
+   		    if (entityName == 'listener')
+   		        hName = "Listener for clients";
+   		    var noAnnotations = stripAnnotations(entityName, ent, annotations);
+			var ediv = entity(entityName, entityName, hName, noAnnotations, undefined);
+			if (ediv.actualName == 'router') {
+				ediv.attributes.filter(function (attr) { return attr.name == 'name'})[0].value = newname;
+				// if we have any new links (connectors), then the router's mode should be interior
+				if (newLinks.length) {
+					var roleAttr = ediv.attributes.filter(function (attr) { return attr.name == 'mode'})[0];
+					roleAttr.value = roleAttr.selected = "interior";
+				}
+			}
+			if (ediv.actualName == 'container') {
+				ediv.attributes.filter(function (attr) { return attr.name == 'containerName'})[0].value = newname + "-container";
+			}
+			if (ediv.actualName == 'listener') {
+				// find max port number that is used in all the listeners
+				ediv.attributes.filter(function (attr) { return attr.name == 'port'})[0].value = ++maxPort;
+			}
+			// special case for required log.module since it doesn't have a default
+			if (ediv.actualName == 'log') {
+				var moduleAttr = ediv.attributes.filter(function (attr) { return attr.name == 'module'})[0];
+				moduleAttr.value = moduleAttr.selected = "DEFAULT";
+			}
+			$scope.entities.push( ediv );
+   		})
+
+		// add a tab for each annotation that was found
+		var annotationEnts = [];
+		for (var key in annotations) {
+			ent = angular.copy(schema.annotations[key]);
+			ent.attributes.name = {type: "string", unique: true, description: "Unique name that is used to refer to this set of attributes."}
+			var ediv = entity(key, key+'tab', QDRService.humanify(key), ent, undefined);
+			ediv.attributes.filter(function (attr) { return attr.name == 'name'})[0].value = key;
+			$scope.entities.push( ediv );
+			annotationEnts.push( ediv );
+		}
+
+		// add an additional listener tab if any links are reversed
+		ent = schema.entityTypes['listener'];
+		newLinks.some(function (link) {
+			if (link.__data__.right) {
+	   		    var noAnnotations = stripAnnotations('listener', ent, annotations);
+				var ediv = entity("listener", "listener0", "Listener (internal)", noAnnotations, undefined);
+				ediv.attributes.filter(function (attr) { return attr.name == 'port'})[0].value = ++maxPort;
+				// connectors from other routers need to connect to this addr:port
+				myPort = maxPort;
+				myAddr = ediv.attributes.filter(function (attr) { return attr.name == 'host'})[0].value
+
+				// override the role. 'normal' is the default, but we want inter-router
+				ediv.attributes.filter(function( attr ) { return attr.name == 'role'})[0].selected = 'inter-router';
+				separatedEntities.push( ediv );
+				return true; // stop looping
+			}
+			return false;   // continue looping
+		})
+
+		// Add connector tabs for each new link on the topology graph
+		ent = schema.entityTypes['connector'];
+		newLinks.forEach(function (link, i) {
+   		    var noAnnotations = stripAnnotations('connector', ent, annotations);
+			var ediv = entity('connector', 'connector' + i, " " + link.__data__.source.name, noAnnotations, link.__data__.right, link)
+
+			// override the connector role. 'normal' is the default, but we want inter-router
+			ediv.attributes.filter(function( attr ) { return attr.name == 'role'})[0].selected = 'inter-router';
+
+			// find the addr:port of the inter-router listener to use
+			var listener = nodeInfo[link.__data__.source.key]['.listener'];
+			var attrs = listener.attributeNames;
+			for (var i=0; i<listener.results.length; ++i) {
+				var res = listener.results[i];
+				var role = QDRService.valFor(attrs, res, 'role');
+				if (role == 'inter-router') {
+					ediv.attributes.filter(function( attr ) { return attr.name == 'host'})[0].value =
+						QDRService.valFor(attrs, res, 'host')
+					ediv.attributes.filter(function( attr ) { return attr.name == 'port'})[0].value =
+						QDRService.valFor(attrs, res, 'port')
+					break;
+				}
+			}
+			if (link.__data__.right) {
+				// connectors from other nodes need to connect to the new router's listener addr:port
+   				ediv.attributes.filter(function (attr) { return attr.name == 'port'})[0].value = myPort;
+   				ediv.attributes.filter(function (attr) { return attr.name == 'host'})[0].value = myAddr;
+
+				separatedEntities.push(ediv)
+			}
+			else
+				$scope.entities.push( ediv );
+		})
+		Array.prototype.push.apply($scope.entities, separatedEntities);
+
+		// update the description on all the annotation tabs
+		annotationEnts.forEach ( function (ent) {
+			var shared = Object.keys(annotations[ent.actualName]);
+			ent.description += " These fields are shared by " + shared.join(" and ") + ".";
+
+		})
+
+		$scope.testPattern = function (attr) {
+			if (attr.rawtype == 'path')
+				return /^(\/)?([^/\0]+(\/)?)+$/;
+				//return /^(.*\/)([^/]*)$/;
+			return /(.*?)/;
+		}
+
+		$scope.attributeDescription = '';
+		$scope.attributeType = '';
+		$scope.attributeRequired = '';
+		$scope.attributeUnique = '';
+		$scope.active = 'router'
+		$scope.fieldsetDivs = "/fieldsetDivs.html"
+		$scope.setActive = function (tabName) {
+			$scope.active = tabName
+		}
+		$scope.isActive = function (tabName) {
+			return $scope.active === tabName
+		}
+		$scope.showDescription = function (attr, e) {
+			$scope.attributeDescription = attr.description;
+			var offset = jQuery(e.currentTarget).offset()
+			jQuery('.attr-description').offset({top: offset.top})
+
+			$scope.attributeType = "Type: " + JSON.stringify(attr.rawtype);
+			$scope.attributeRequired = attr.required ? 'required' : '';
+			$scope.attributeUnique = attr.unique ? 'Must be unique' : '';
+		}
+        // handle the download button click
+        // copy the dialog's values to the original node
+        $scope.download = function () {
+	        dialog.close({entities: $scope.entities, annotations: annotations});
+        }
+        $scope.cancel = function () {
+            dialog.close()
+        };
+
+		$scope.selectAnnotationTab = function (tabName) {
+            var tabs = $( "#tabs" ).tabs();
+            tabs.tabs("select", tabName);
+		}
+
+        var initTabs = function () {
+            var div = angular.element("#tabs");
+            if (!div.width()) {
+                setTimeout(initTabs, 100);
+                return;
+            }
+            $( "#tabs" )
+                .tabs()
+                .addClass('ui-tabs-vertical ui-helper-clearfix');
+        }
+        // start the update loop
+        initTabs();
+
+  };
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/templates/dispatch/base.html
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/templates/dispatch/base.html b/console/dispatch-dashboard/dispatch/templates/dispatch/base.html
new file mode 100644
index 0000000..e0c28e6
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/templates/dispatch/base.html
@@ -0,0 +1,10 @@
+{% extends 'base.html' %}
+
+{% block sidebar %}
+  {% include 'horizon/common/_sidebar.html' %}
+{% endblock %}
+
+{% block main %}
+    {% include "horizon/_messages.html" %}
+    {% block dispatch_main %}{% endblock %}
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/topology/__init__.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/topology/__init__.py b/console/dispatch-dashboard/dispatch/topology/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/topology/panel.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/topology/panel.py b/console/dispatch-dashboard/dispatch/topology/panel.py
new file mode 100644
index 0000000..6efbeda
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/topology/panel.py
@@ -0,0 +1,20 @@
+# Licensed 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.
+
+from django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+
+class Topology(horizon.Panel):
+    name = _("Topology")
+    slug = "topology"

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/topology/templates/topology/index.html
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/topology/templates/topology/index.html b/console/dispatch-dashboard/dispatch/topology/templates/topology/index.html
new file mode 100644
index 0000000..80d76bd
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/topology/templates/topology/index.html
@@ -0,0 +1,35 @@
+{% extends 'base.html' %}
+{% load breadcrumb_nav %}
+{% load i18n %}
+{% block title %}{% trans "Topology" %}{% endblock %}
+
+{% block content %}
+<div class='topbar'>
+  {% include "header/_header.html" %}
+</div>
+<div id='main_content' class="topology-container">
+  {% include "horizon/_messages.html" %}
+  {% block sidebar %}
+    {% include 'horizon/common/_sidebar.html' %}
+  {% endblock %}
+  <div id='content_body'>
+    <div class='container-fluid'>
+      <div class="row">
+        <div class="col-xs-12">
+          <div class="page-breadcrumb">
+            {% block breadcrumb_nav %}
+              {% breadcrumb_nav %}
+            {% endblock %}
+          </div>
+
+          {% block page_header %}
+          {% endblock %}
+          {% block main %}
+          <ng-include src="'dispatch/topology.html'"></ng-include>
+          {% endblock %}
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock %}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/topology/tests.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/topology/tests.py b/console/dispatch-dashboard/dispatch/topology/tests.py
new file mode 100644
index 0000000..b321fc1
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/topology/tests.py
@@ -0,0 +1,19 @@
+# Licensed 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.
+
+from horizon.test import helpers as test
+
+
+class TopologyTests(test.TestCase):
+    # Unit tests for topology.
+    def test_me(self):
+        self.assertTrue(1 + 1 == 2)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/topology/urls.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/topology/urls.py b/console/dispatch-dashboard/dispatch/topology/urls.py
new file mode 100644
index 0000000..d8629f9
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/topology/urls.py
@@ -0,0 +1,20 @@
+# Licensed 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.
+
+from django.conf.urls import url
+
+from dispatch.topology import views
+
+
+urlpatterns = [
+    url(r'^$', views.IndexView.as_view(), name='index'),
+]

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/topology/views.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/topology/views.py b/console/dispatch-dashboard/dispatch/topology/views.py
new file mode 100644
index 0000000..03b6012
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/topology/views.py
@@ -0,0 +1,22 @@
+# Licensed 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.
+
+from horizon import views
+
+
+class IndexView(views.APIView):
+    # A very simple class-based view...
+    template_name = 'dispatch/topology/index.html'
+
+    def get_data(self, request, context, *args, **kwargs):
+        # Add data to the context here...
+        return context

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/enabled/_4000_dispatch.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/enabled/_4000_dispatch.py b/console/dispatch-dashboard/enabled/_4000_dispatch.py
new file mode 100644
index 0000000..16d3215
--- /dev/null
+++ b/console/dispatch-dashboard/enabled/_4000_dispatch.py
@@ -0,0 +1,33 @@
+#    Licensed 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 name of the dashboard to be added to HORIZON['dashboards']. Required.
+DASHBOARD = 'dispatch'
+
+DEFAULT = False
+
+ADD_EXCEPTIONS = {}
+
+# If set to True, this dashboard will not be added to the settings.
+DISABLED = False
+
+# A list of applications to be added to INSTALLED_APPS.
+ADD_INSTALLED_APPS = ['dispatch']
+ADD_ANGULAR_MODULES = [
+    'horizon.dashboard.dispatch',
+]
+
+ADD_SCSS_FILES = [
+    'dashboard/dispatch/dispatch.scss',
+]
+
+AUTO_DISCOVER_STATIC_FILES = True

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/enabled/_4030_dispatch_overv_panel.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/enabled/_4030_dispatch_overv_panel.py b/console/dispatch-dashboard/enabled/_4030_dispatch_overv_panel.py
new file mode 100644
index 0000000..9431be6
--- /dev/null
+++ b/console/dispatch-dashboard/enabled/_4030_dispatch_overv_panel.py
@@ -0,0 +1,9 @@
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'overv'
+# The slug of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'dispatch'
+# The slug of the panel group the PANEL is associated with.
+PANEL_GROUP = 'default'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = 'dispatch.overv.panel.Overv'

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/enabled/_4050_dispatch_topology_panel.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/enabled/_4050_dispatch_topology_panel.py b/console/dispatch-dashboard/enabled/_4050_dispatch_topology_panel.py
new file mode 100644
index 0000000..c641594
--- /dev/null
+++ b/console/dispatch-dashboard/enabled/_4050_dispatch_topology_panel.py
@@ -0,0 +1,9 @@
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'topology'
+# The slug of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'dispatch'
+# The slug of the panel group the PANEL is associated with.
+PANEL_GROUP = 'default'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = 'dispatch.topology.panel.Topology'

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/setup.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/setup.py b/console/dispatch-dashboard/setup.py
new file mode 100644
index 0000000..1815410
--- /dev/null
+++ b/console/dispatch-dashboard/setup.py
@@ -0,0 +1,42 @@
+#! /usr/bin/env python
+#
+# Licensed 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.
+
+from setuptools import setup, find_packages
+
+setup(
+    name = 'dispatch',
+    version = '0.0.1',
+    description = 'sample dashboard extension for OpenStack Dashboard',
+    author = 'Cindy Lu',
+    author_email = 'clu@us.ibm.com',
+    classifiers = [
+        'Environment :: OpenStack',
+        'Framework :: Django',
+        'Intended Audience :: Developers',
+        'Intended Audience :: System Administrators',
+        'License :: OSI Approved :: Apache Software License',
+        'Operating System :: OS Independent',
+        'Operating System :: POSIX :: Linux',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.7',
+        'Topic :: Internet :: WWW/HTTP',
+    ],
+    packages=find_packages(),
+    include_package_data = True,
+)
+
+
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[08/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/d3.v3.min.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/d3.v3.min.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/d3.v3.min.js
new file mode 100644
index 0000000..1664873
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/d3.v3.min.js
@@ -0,0 +1,5 @@
+!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this
 ._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++i<u;)(t=r[i].on)&&t.apply(this,arguments);return n}var e=[],r=new c;return t.on=function(t,i){var u,o=r.get(t);return arguments.length<2?o&&o.on:(o&&(o.on=null,e=e.slice(0,u=e.indexOf(o)).concat(e.slice(u+1)),r.remove(t)),i&&e.push(r.set(t,{on:i})),n)},t}function S(){ao.event.preventDefault()}function k(){for(var n,t=ao.event;n=t.sourceEvent;)t=n;return t}function N(n){for(var t=new _,e=0,r=arguments.length;++e<r;)t[arguments[e]]=w(t);return t.of
 =function(e,r){return function(i){try{var u=i.sourceEvent=ao.event;i.target=n,ao.event=i,t[i.type].apply(e,r)}finally{ao.event=u}}},t}function E(n){return ko(n,Co),n}function A(n){return"function"==typeof n?n:function(){return No(n,this)}}function C(n){return"function"==typeof n?n:function(){return Eo(n,this)}}function z(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function i(){this.setAttribute(n,t)}function u(){this.setAttributeNS(n.space,n.local,t)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=ao.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?a:o:n.local?u:i}function L(n){return n.trim().replace(/\s+/g," ")}function q(n){return new RegExp("(?:^|\\s+)"+ao.requote(n)+"(?:\\s+|$)","g")}function T(n){return(n+"").trim().split(/^|\s
 +/)}function R(n,t){function e(){for(var e=-1;++e<i;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<i;)n[e](this,r)}n=T(n).map(D);var i=n.length;return"function"==typeof t?r:e}function D(n){var t=q(n);return function(e,r){if(i=e.classList)return r?i.add(n):i.remove(n);var i=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(i)||e.setAttribute("class",L(i+" "+n))):e.setAttribute("class",L(i.replace(t," ")))}}function P(n,t,e){function r(){this.style.removeProperty(n)}function i(){this.style.setProperty(n,t,e)}function u(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?u:i}function U(n,t){function e(){delete this[n]}function r(){this[n]=t}function i(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?i:r}function j(n){function t(){var t=this.ownerDocument,e=this.namespaceURI;return e===zo&&t.documentElement.namespaceURI===zo?
 t.createElement(n):t.createElementNS(e,n)}function e(){return this.ownerDocument.createElementNS(n.space,n.local)}return"function"==typeof n?n:(n=ao.ns.qualify(n)).local?e:t}function F(){var n=this.parentNode;n&&n.removeChild(this)}function H(n){return{__data__:n}}function O(n){return function(){return Ao(this,n)}}function I(n){return arguments.length||(n=e),function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}function Y(n,t){for(var e=0,r=n.length;r>e;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t<l;);return o}}function X(n,t,e){function r(){var t=this[o];t&&(this.removeEventListener(n,t,t.$),delete this[o])}function i(){var i=l(t,co(arguments));r.call(this),this.addEventListener(n,this[o]=i,i.$=e),i._=t}function u(){var t,e=new RegExp("^__on([^.]+)"+ao.requote(n)+"$");for(var r in this)if(t=r.matc
 h(e)){var i=this[r];this.removeEventListener(t[1],i,i.$),delete this[r]}}var o="__on"+n,a=n.indexOf("."),l=$;a>0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select
 ("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn
 ,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.53713
 85*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.g
 et(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),
 Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(
 2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var 
 n,t=oa,e=1/0;t;)t.c?(t.t<e&&(e=t.t),t=(n=t).n):t=n?n.n=t.n:oa=t.n;return aa=n,e}function Pn(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function Un(n,t){var e=Math.pow(10,3*xo(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p
 ="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e
 ),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++a<r;)37===n.charCodeAt(a)&&(o.push(n.slice(l,a)),null!=(i=ya[e=n.charAt(++a)])&&(e=n.charAt(++a)),(u=A[e])&&(e=u(t,null==i?"e"===e?" ":"0":i)),o.push(e),l=a+1);return o.push(n.slice(l,a)),o.join("")}var r=n.length;return t.parse=function(t){var r={y:1900,m:0,d:1,H:0,M:0,S:0,L:0,Z:null},i=e(r,n,t,0);if(i!=t.length)return null;"p"in r&&(r.H=r.H%12+12*r.p);var u=null!=r.Z&&va!==Hn,o=new(u?Hn:va);return"j"in r?o.setFullYear(r.y,0,r.j):"W"in r||"U"in r?("w"in r||(
 r.w="W"in r?1:0),o.setFullYear(r.y,0,1),o.setFullYear(r.y,0,"W"in r?(r.w+6)%7+7*r.W-(o.getDay()+5)%7:r.w+7*r.U-(o.getDay()+6)%7)):o.setFullYear(r.y,r.m,r.d),o.setHours(r.H+(r.Z/100|0),r.M+r.Z%100,r.S,r.L),u?o._:o},t.toString=function(){return n},t}function e(n,t,e,r){for(var i,u,o,a=0,l=t.length,c=e.length;l>a;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}fun
 ction l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:fu
 nction(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e<r;)t.set(n[e].toLowerCase(),e);return t}function $n(n,t,e){ma.lastIn
 dex=0;var r=ma.exec(t.slice(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function Bn(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e));return r?(n.U=+r[0],e+r[0].length):-1}function Wn(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e));return r?(n.W=+r[0],e+r[0].length):-1}function Jn(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Gn(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.y=Qn(+r[0]),e+r[0].length):-1}function Kn(n,t,e){return/^[+-]\d{4}$/.test(t=t.slice(e,e+5))?(n.Z=-t,e+5):-1}function Qn(n){return n+(n>68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].len
 gth):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e<t;)n[e][0]=this(n[e][0]);return function(t){for(var e=0,r=n[e];!r[1](t);)r=n[++e];return r[0](t)}}function ft(){}function st(n,t,e){var r=e.s=n+t,i=r-n,u=r-i;e.t=n-u+(t-i)}function ht(n,t){n&&wa.hasOwnProperty(n.type)&&wa[n.type](n,t)}function pt(n,t,e){var r,i=-1,u=n.length-e;for(t.lineStart();++i<u;)r=n[i],t.point(r[0],r[1],r[2]);t.lineEnd()}function gt(n,t){var e=-1,r=n.length;for(t.polygonStart();++e<r;)pt(n[e],t,1);t.polygonEnd
 ()}function vt(){function n(n,t){n*=Yo,t=t*Yo/2+Fo/4;var e=n-r,o=e>=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])<Uo&&xo(n[1]-t[1])<Uo}function St(n,t){n*=Yo;var e=Math.cos(t*=Yo);kt(e*Math.cos(n),e*Math.sin(n),Math.sin(t))}function kt(n,t,e){++Ea,Ca+=(n-Ca)/Ea,za+=(t-za)/Ea,La+=
 (e-La)/Ea}function Nt(){function n(n,i){n*=Yo;var u=Math.cos(i*=Yo),o=u*Math.cos(n),a=u*Math.sin(n),l=Math.sin(i),c=Math.atan2(Math.sqrt((c=e*l-r*a)*c+(c=r*o-t*l)*c+(c=t*a-e*o)*c),t*o+e*a+r*l);Aa+=c,qa+=c*(t+(t=o)),Ta+=c*(e+(e=a)),Ra+=c*(r+(r=l)),kt(t,e,r)}var t,e,r;ja.point=function(i,u){i*=Yo;var o=Math.cos(u*=Yo);t=o*Math.cos(i),e=o*Math.sin(i),r=Math.sin(u),ja.point=n,kt(t,e,r)}}function Et(){ja.point=St}function At(){function n(n,t){n*=Yo;var e=Math.cos(t*=Yo),o=e*Math.cos(n),a=e*Math.sin(n),l=Math.sin(t),c=i*l-u*a,f=u*o-r*l,s=r*a-i*o,h=Math.sqrt(c*c+f*f+s*s),p=r*o+i*a+u*l,g=h&&-nn(p)/h,v=Math.atan2(h,p);Da+=g*c,Pa+=g*f,Ua+=g*s,Aa+=v,qa+=v*(r+(r=o)),Ta+=v*(i+(i=a)),Ra+=v*(u+(u=l)),kt(r,i,u)}var t,e,r,i,u;ja.point=function(o,a){t=o,e=a,ja.point=n,o*=Yo;var l=Math.cos(a*=Yo);r=l*Math.cos(o),i=l*Math.sin(o),u=Math.sin(a),kt(r,i,u)},ja.lineEnd=function(){n(t,e),ja.lineEnd=Et,ja.point=St}}function Ct(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.inv
 ert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function zt(){return!0}function Lt(n,t,e,r,i){var u=[],o=[];if(n.forEach(function(n){if(!((t=n.length-1)<=0)){var t,e=n[0],r=n[t];if(wt(e,r)){i.lineStart();for(var a=0;t>a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r<t;)i.n=e=n[r],e.p=i,i=e;i.n=e=n[0],e.p=i}}function Tt(n,t,e,r){this.x=n,t
 his.z=t,this.o=e,this.e=r,this.v=!1,this.n=this.p=null}function Rt(n,t,e,r){return function(i,u){function o(t,e){var r=i(t,e);n(t=r[0],e=r[1])&&u.point(t,e)}function a(n,t){var e=i(n,t);d.point(e[0],e[1])}function l(){m.point=a,d.lineStart()}function c(){m.point=o,d.lineEnd()}function f(n,t){v.push([n,t]);var e=i(n,t);x.point(e[0],e[1])}function s(){x.lineStart(),v=[]}function h(){f(v[0][0],v[0][1]),x.lineEnd();var n,t=x.clean(),e=M.buffer(),r=e.length;if(v.pop(),g.push(v),v=null,r)if(1&t){n=e[0];var i,r=n.length-1,o=-1;if(r>0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o<r;)u.point((i=n[o])[0],i[1]);u.lineEnd()}}else r>1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonSta
 rt(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)<Uo?(n.point(e,r=(r+o)/2>0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)<Uo&&(e-=i*Uo),xo(u-a)<Uo&&(u-=a*Uo),r=Ft(e,r,u,o),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),t=0),n.point(e=u,r=o),i=a},lineEnd:function(){n.lineEnd(),e=r=
 NaN},clean:function(){return 2-t}}}function Ft(n,t,e,r){var i,u,o=Math.sin(n-e);return xo(o)>Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]<t[0]?Fo:-Fo;i=e*u/2,r.point(-u,i),r.point(0,i),r.point(u,i)}else r.point(t[0],t[1])}function Ot(n,t){var e=n[0],r=n[1],i=[Math.sin(e),-Math.cos(e),0],u=0,o=0;ka.reset();for(var a=0,l=t.length;l>a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r==
 =A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s)
 ,v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)<Uo,C=A||Uo>E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)<Uo?k:N):k<=b[1]&&b[1]<=N:E>Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&
 (h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)<Uo?i>0?0:3:xo(r[0]-e)<Uo?i>0?2:1:xo(r[1]-t)<Uo?i>0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=
 Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=M
 ath.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,G
 t(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,
 w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){
+r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)<Uo||xo(r-h)<Uo?(r+h)/2:Math.atan2(_,b),E=n(N,k),A=E[0],C=E[1],z=A-t,L=C-e,q=M*z-m*L;(q*q/x>u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:functio
 n(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return argum
 ents.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math
 .sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s
 ,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.inver
 t=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)<Uo?ce:(e.invert=function(n,t){var e=u-t;return[Math.atan2(n,e)/i,u-K(i)*Math.sqrt(n*n+e*e)]},e)}function Ne(n,t){return[n,Math.log(Math.tan(Fo/4+t/2))]}function Ee(n){var t,e=oe(n),r=e.scale,i=e.translate,u=e.clipExtent;return e.scale=function(){var n=r.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.translate=function(){var n=i.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.clipExtent=function(n){var o=u.apply(e,arguments);if(o===e){if(t=null==n){var a=Fo*r(),l=i();u([[l[0]-a,l[1]-a],[l[0]+a,l[1]+a]])}}else t&&(o=null);return o},e.clipExtent(null)}function Ae(n,t){return[Math.log(Math.tan(Fo/4+t/2)),-n]}function Ce(n){return n[0]}function ze(n){return n[1]}function Le(n){for(va
 r t=n.length,e=[0,1],r=2,i=2;t>i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)<Uo&&xo(r-l.circle.cy)<Uo;)u=l.P,a.unshift(l),je(l),l=u;a.unshift(l),Be(l);for(var c=o;c.circle&&xo(e-c.circle.x)<Uo&&xo(r-c.circle.cy)<Uo;)o=c.N,a.push(c),je(c),c=o;a.push(c),Be(c);var f,s=a.length;for(f=1;s>f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-
 1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Z
 e(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)<Uo&&g-i>Uo?{x:s,y:xo(t-s)<Uo?e:g}:xo(i-g)<Uo&&h-r>Uo?{x:xo(e-g)<Uo?t:h,y:g}:xo(r-h)<Uo&&i-p>Uo?{x:h,y:xo(t-h)<Uo?e:p}:xo(i-p)<Uo&&r-s>Uo?{x:xo(e-p)<Uo?t:s,y:p}:null),u.site,null)),++l)}function Ve(n,t){return t.angle-n.angle}function Xe(){rr(this),this.x=this.y=this.arc=this.site=this.cy=null}function $e(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,i=n.site,u=e.site;if(r!==u){var o=i.x,a=i.y,l=r.x-o,c=r.y-a,f=u.x-o,s=u.y-a,h=2*(l*s-c*f);if(!(h>=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.y<M.y||y.y===M.y&&y.x<=M.x){if(!M.L){m=M.P;break}M=M.L}else{if(!M.R){m=M;break}M=M.R}ll.insert(
 m,y),m||(al=y)}}}}function Be(n){var t=n.circle;t&&(t.P||(al=t.N),ll.remove(t),fl.push(t),rr(t),n.circle=null)}function We(n){for(var t,e=il,r=Yt(n[0][0],n[0][1],n[1][0],n[1][1]),i=e.length;i--;)t=e[i],(!Je(t,n)||!r(t)||xo(t.a.x-t.b.x)<Uo&&xo(t.a.y-t.b.y)<Uo)&&(t.a=t.b=null,e.splice(i,1))}function Je(n,t){var e=n.b;if(e)return!0;var r,i,u=n.a,o=t[0][0],a=t[1][0],l=t[0][1],c=t[1][1],f=n.l,s=n.r,h=f.x,p=f.y,g=s.x,v=s.y,d=(h+g)/2,y=(p+v)/2;if(v===p){if(o>d||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.y<l)return}else u={x:d,y:c};e={x:d,y:l}}}else if(r=(h-g)/(v-p),i=y-r*d,-1>r||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.y<l)return}else u={x:(c-i)/r,y:c};e={x:(l-i)/r,y:l}}else if(v>p){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.x<o)return}else u={x:a,y:r*a+i};e={x:o,y:r*o+i}}return n.a=u,n.b=e,!0}function Ge(n,t){this.l=n,this.r=t,this.a=this.b=null}function Ke(n,t,
 e,r){var i=new Ge(n,t);return il.push(i),e&&nr(i,n,t,e),r&&nr(i,t,n,r),ul[n.i].edges.push(new tr(i,n,t)),ul[t.i].edges.push(new tr(i,t,n)),i}function Qe(n,t,e){var r=new Ge(n,null);return r.a=t,r.b=e,il.push(r),r}function nr(n,t,e,r){n.a||n.b?n.l===e?n.b=r:n.a=r:(n.a=r,n.l=t,n.r=e)}function tr(n,t,e){var r=n.a,i=n.b;this.edge=n,this.site=t,this.angle=e?Math.atan2(e.y-t.y,e.x-t.x):n.l===t?Math.atan2(i.x-r.x,r.y-i.y):Math.atan2(r.x-i.x,i.y-r.y)}function er(){this._=null}function rr(n){n.U=n.C=n.L=n.R=n.P=n.N=null}function ir(n,t){var e=t,r=t.R,i=e.U;i?i.L===e?i.L=r:i.R=r:n._=r,r.U=i,e.U=r,e.R=r.L,e.R&&(e.R.U=e),r.L=e}function ur(n,t){var e=t,r=t.L,i=e.U;i?i.L===e?i.L=r:i.R=r:n._=r,r.U=i,e.U=r,e.L=r.R,e.L&&(e.L.U=e),r.R=e}function or(n){for(;n.L;)n=n.L;return n}function ar(n,t){var e,r,i,u=n.sort(lr).pop();for(il=[],ul=new Array(n.length),ol=new er,ll=new er;;)if(i=al,u&&(!i||u.y<i.y||u.y===i.y&&u.x<i.x))u.x===e&&u.y===r||(ul[u.i]=new Ye(u),He(u),e=u.x,r=u.y),u=n.pop();else{if(!i)break
 ;Fe(i.arc)}t&&(We(t),Ze(t));var o={cells:ul,edges:il};return ol=ll=il=ul=null,o}function lr(n,t){return t.y-n.y||t.x-n.x}function cr(n,t,e){return(n.x-e.x)*(t.y-n.y)-(n.x-t.x)*(e.y-n.y)}function fr(n){return n.x}function sr(n){return n.y}function hr(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function pr(n,t,e,r,i,u){if(!n(t,e,r,i,u)){var o=.5*(e+i),a=.5*(r+u),l=t.nodes;l[0]&&pr(n,l[0],e,r,o,a),l[1]&&pr(n,l[1],o,r,i,a),l[2]&&pr(n,l[2],e,a,o,u),l[3]&&pr(n,l[3],o,a,i,u)}}function gr(n,t,e,r,i,u,o){var a,l=1/0;return function c(n,f,s,h,p){if(!(f>u||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function
 (n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return u<t.length&&(i=t.slice(u),a[o]?a[o]+=i:a[++o]=i),a.length<2?l[0]?(t=l[0].x,function(n){return t(n)+""}):function(){return t}:(t=l.length,function(n){for(var e,r=0;t>r;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e
 ;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)
 *n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]<e[0]*t[1]&&(t[0]*=-1,t[1]*=-1,r*=-1,i*=-1),this.rotate=(r?Math.atan2(t[1],t[0]):Math.atan2(-e[0],e[1]))*Zo,this.translate=[n.e,n.f],this.scale=[r,u],this.skew=u?Math.atan2(i,u)*Zo:0}function
  Fr(n,t){return n[0]*t[0]+n[1]*t[1]}function Hr(n){var t=Math.sqrt(Fr(n,n));return t&&(n[0]/=t,n[1]/=t),t}function Or(n,t,e){return n[0]+=e*t[0],n[1]+=e*t[1],n}function Ir(n){return n.length?n.pop()+",":""}function Yr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push("translate(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else(t[0]||t[1])&&e.push("translate("+t+")")}function Zr(n,t,e,r){n!==t?(n-t>180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.r
 otate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i<u;)e[(t=r[i]).i]=t.x(n);return e.join("")}}function Br(n,t){return t=(t-=n=+n)||1/t,function(e){return(e-n)/t}}function Wr(n,t){return t=(t-=n=+n)||1/t,function(e){return Math.max(0,Math.min(1,(e-n)/t))}}function Jr(n){for(var t=n.source,e=n.target,r=Kr(t,e),i=[t];t!==r;)t=t.parent,i.push(t);for(var u=i.length;e!==r;)i.splice(u,0,e),e=e.parent;return i}function Gr(n){for(var t=[],e=n.parent;null!=e;)t.push(n),n=e,e=e.parent;return t.push(n),t}function Kr(n,t){if(n===t)return n;for(var e=Gr(n),r=Gr(t),i=e.pop(),u=r.pop(),o=null;i===u;)o=i,i=e.pop(),u=r.pop();return o}function Qr(n){n.fixed|=2}function ni(n){n.fixed&=-7}function ti(n){n.fixed|=4,n.px=n.x,n.py=n.y}function ei(n){n.fixed&=-5}function ri(n,t,e){var r=0,i=0;if(n.charge=0,!n.leaf)for(var u,o=n.nodes,a=o.length,l=-1;++l<a;)u=o[l],null!=u&&(ri(u,t,e),n.charge+=u.charge,r+=u.charge*u.cx,i+=u.charge*u.cy);if(n.point){n.l
 eaf||(n.point.x+=Math.random()-.5,n.point.y+=Math.random()-.5);var c=t*e[n.point.index];n.charge+=n.pointCharge=c,r+=c*n.point.x,i+=c*n.point.y}n.cx=r/n.charge,n.cy=i/n.charge}function ii(n,t){return ao.rebind(n,t,"sort","children","value"),n.nodes=n,n.links=fi,n}function ui(n,t){for(var e=[n];null!=(n=e.pop());)if(t(n),(i=n.children)&&(r=i.length))for(var r,i;--r>=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++o<i;)e.push(u[o]);for(;null!=(n=r.pop());)t(n)}function ai(n){return n.children}function li(n){return n.value}function ci(n,t){return t.value-n.value}function fi(n){return ao.merge(n.map(function(n){return(n.children||[]).map(function(t){return{source:n,target:t}})}))}function si(n){return n.x}function hi(n){return n.y}function pi(n,t,e){n.y0=t,n.y=e}function gi(n){return ao.range(n.length)}function vi(n){for(var t=-1,e=n[0].length,r=[];++t<e;)r[t]=0;return r}function di(n){for(var t,e=1,r=0,
 i=n[0][1],u=n.length;u>e;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_
 next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.r<r.r?Si(r,i=a):Si(r=l,i),o--):(wi(r,u),i=u,t(u))}var y=(f+s)/2,m=(h+p)/2,M=0;for(o=0;c>o;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u<o;)Ci(i[u],t,e,r)}function zi(n,t,e){var r=n.r+e.r,i=t.x-n.x,u=t.y-n.y;if(r&&(i||u)){var o=t.r+e.r,a=i*i+u*u;o*=o,r*=r;var l=.5+(r-o)/(2*a),c=Math.sqrt(Math.max(0,2*o*(r+a)-(r-=a)*r-o*o))/(2*a);e.x=n.x+l*i+c*u,e.y=n.y+l*u-c*i}else e.x=n.x+r,e.y=n.y}function Li(n,t){return n.parent==t.parent?1:2}function qi(n){var t=n.children;return t.length?t[0]:n.t}function Ti(n){var t,e=n.children;return(t=e.length)?e[t-1]:n.t}function Ri(n,t,e){var r=e/(t.i-n.i);t.c-=r,t.s+=e,n.c+
 =r,t.z+=e,t.m+=e}function Di(n){for(var t,e=0,r=0,i=n.children,u=i.length;--u>=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{f
 loor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]<n[0]&&(n=n.slice().reverse(),t=t.slice().reverse());++o<=a;)i.push(e(n[o-1],n[o])),u.push(r(t[o-1],t[o]));return function(t){var e=ao.bisect(n,t,1,a)-1;return u[e](i[e](t))}}function Wi(n,t,e,r){function i(){var i=Math.min(n.length,t.length)>2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,
 t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?
 Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++<f;)for(var h=s-1;h>0;h--)o.push(u(c)*h);for(c=0;o[c]<a;c++);for(f=o.length;o[f-1]>l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n)
 :""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++o<a;)i.has(u=r[o])||i.set(u,n.push(u));return e[t.t].apply(e,t.a)},e.range=function(n){return arg
 uments.length?(u=n,o=0,t={t:"range",a:arguments},e):u},e.rangePoints=function(i,a){arguments.length<2&&(a=0);var l=i[0],c=i[1],f=n.length<2?(l=(l+c)/2,0):(c-l)/(n.length-1+a);return u=r(l+f*a/2,f),o=0,t={t:"rangePoints",a:arguments},e},e.rangeRoundPoints=function(i,a){arguments.length<2&&(a=0);var l=i[0],c=i[1],f=n.length<2?(l=c=Math.round((l+c)/2),0):(c-l)/(n.length-1+a)|0;return u=r(l+Math.round(f*a/2+(c-l-(n.length-1+a)*f)/2),f),o=0,t={t:"rangeRoundPoints",a:arguments},e},e.rangeBands=function(i,a,l){arguments.length<2&&(a=0),arguments.length<3&&(l=a);var c=i[1]<i[0],f=i[c-0],s=i[1-c],h=(s-f)/(n.length-a+2*l);return u=r(f+h*l,h),c&&u.reverse(),o=h*(1-a),t={t:"rangeBands",a:arguments},e},e.rangeRoundBands=function(i,a,l){arguments.length<2&&(a=0),arguments.length<3&&(l=a);var c=i[1]<i[0],f=i[c-0],s=i[1-c],h=Math.floor((s-f)/(n.length-a+2*l));return u=r(f+Math.round((s-f-(n.length-a)*h)/2),h),c&&u.reverse(),o=Math.round(h*(1-a)),t={t:"rangeRoundBands",a:arguments},e},e.rangeBand=fu
 nction(){return o},e.rangeExtent=function(){return Yi(t.a[0])},e.copy=function(){return ou(n,t)},e.domain(n)}function au(n,t){function u(){var e=0,r=t.length;for(a=[];++e<r;)a[e-1]=ao.quantile(n,e/r);return o}function o(n){return isNaN(n=+n)?void 0:t[ao.bisect(a,n)]}var a;return o.domain=function(t){return arguments.length?(n=t.map(r).filter(i).sort(e),u()):n},o.range=function(n){return arguments.length?(t=n,u()):t},o.quantiles=function(){return a},o.invertExtent=function(e){return e=t.indexOf(e),0>e?[NaN,NaN]:[e>0?a[e-1]:n[0],e<a.length?a[e]:n[n.length-1]]},o.copy=function(){return au(n,t)},u()}function lu(n,t,e){function r(t){return e[Math.max(0,Math.min(o,Math.floor(u*(t-n))))]}function i(){return u=e.length/(t-n),o=e.length-1,r}var u,o;return r.domain=function(e){return arguments.length?(n=+e[0],t=+e[e.length-1],i()):[n,t]},r.range=function(n){return arguments.length?(e=n,i()):e},r.invertExtent=function(t){return t=e.indexOf(t),t=0>t?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return
  lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math
 .sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s<h;)i.call(this,l=t[s],s)?f.push([+p.call(this,l,s),+g.call(this,l,s)]):f.length&&(o(),f=[]);return f.length&&o(),c.length?c.join(""):null}var e=Ce,r=ze,i=zt,u=xu,o=u.key,a=.7;return t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.defined=function(n){return arguments.length?(i=n,t):i},t.interpolate=function(n){return arguments.length?(o="function"==typeof n?u=n:(u=Tl.get(n)||xu).key,t):o},t.tension=function(n){return arguments.length?(a=n,t):a},t}function xu(n){return n.length>1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t<e;)i.push("H",(r[0]+(r=n[t])[0])/2,"V",r[1])
 ;return e>1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t<e;)i.push("V",(r=n[t])[1],"H",r[0]);return i.join("")}function Su(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t<e;)i.push("H",(r=n[t])[0],"V",r[1]);return i.join("")}function ku(n,t){return n.length<4?xu(n):n[1]+Au(n.slice(1,-1),Cu(n,t))}function Nu(n,t){return n.length<3?bu(n):n[0]+Au((n.push(n[0]),n),Cu([n[n.length-2]].concat(n,[n[1]]),t))}function Eu(n,t){return n.length<3?xu(n):n[0]+Au(n,Cu(n,t))}function Au(n,t){if(t.length<1||n.length!=t.length&&n.length!=t.length+2)return xu(n);var e=n.length!=t.length,r="",i=n[0],u=n[1],o=t[0],a=o,l=1;if(e&&(r+="Q"+(u[0]-2*o[0]/3)+","+(u[1]-2*o[1]/3)+","+u[0]+","+u[1],i=n[1],l=2),t.length>1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c<t.length;c++,l++)u=n[l],a=t[c],r+="S"+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1]}if(e){var f=n[l];r+="Q"+(u[0]+2*a[0]/3
 )+","+(u[1]+2*a[1]/3)+","+f[0]+","+f[1]}return r}function Cu(n,t){for(var e,r=[],i=(1-t)/2,u=n[0],o=n[1],a=1,l=n.length;++a<l;)e=u,u=o,o=n[a],r.push([i*(o[0]-e[0]),i*(o[1]-e[1])]);return r}function zu(n){if(n.length<3)return xu(n);var t=1,e=n.length,r=n[0],i=r[0],u=r[1],o=[i,i,i,(r=n[1])[0]],a=[u,u,u,r[1]],l=[i,",",u,"L",Ru(Pl,o),",",Ru(Pl,a)];for(n.push(n[e-1]);++t<=e;)r=n[t],o.shift(),o.push(r[0]),a.shift(),a.push(r[1]),Du(l,o,a);return n.pop(),l.push("L",r),l.join("")}function Lu(n){if(n.length<4)return xu(n);for(var t,e=[],r=-1,i=n.length,u=[0],o=[0];++r<3;)t=n[r],u.push(t[0]),o.push(t[1]);for(e.push(Ru(Pl,u)+","+Ru(Pl,o)),--r;++r<i;)t=n[r],u.shift(),u.push(t[0]),o.shift(),o.push(t[1]),Du(e,u,o);return e.join("")}function qu(n){for(var t,e,r=-1,i=n.length,u=i+4,o=[],a=[];++r<4;)e=n[r%i],o.push(e[0]),a.push(e[1]);for(t=[Ru(Pl,o),",",Ru(Pl,a)],--r;++r<u;)e=n[r%i],o.shift(),o.push(e[0]),a.shift(),a.push(e[1]),Du(t,o,a);return t.join("")}function Tu(n,t){var e=n.length-1;if(e)for(va
 r r,i,u=n[0][0],o=n[0][1],a=n[e][0]-u,l=n[e][1]-o,c=-1;++c<=e;)r=n[c],i=c/e,r[0]=t*r[0]+(1-t)*(u+i*a),r[1]=t*r[1]+(1-t)*(o+i*l);return zu(n)}function Ru(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]+n[3]*t[3]}function Du(n,t,e){n.push("C",Ru(Rl,t),",",Ru(Rl,e),",",Ru(Dl,t),",",Ru(Dl,e),",",Ru(Pl,t),",",Ru(Pl,e))}function Pu(n,t){return(t[1]-n[1])/(t[0]-n[0])}function Uu(n){for(var t=0,e=n.length-1,r=[],i=n[0],u=n[1],o=r[0]=Pu(i,u);++t<e;)r[t]=(o+(o=Pu(i=u,u=n[t+1])))/2;return r[t]=o,r}function ju(n){for(var t,e,r,i,u=[],o=Uu(n),a=-1,l=n.length-1;++a<l;)t=Pu(n[a],n[a+1]),xo(t)<Uo?o[a]=o[a+1]=0:(e=o[a]/t,r=o[a+1]/t,i=e*e+r*r,i>9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i<u;)t=n[i],e=t[0],r=t[1]-Io,t[0]=e*Math.cos(r),t[1]=e*Math.sin(r);return n}function Ou(n){function t(t
 ){function l(){v.push("M",a(n(y),s),f,c(n(d.reverse()),s),"Z")}for(var h,p,g,v=[],d=[],y=[],m=-1,M=t.length,x=En(e),b=En(i),_=e===r?function(){
+return p}:En(r),w=i===u?function(){return g}:En(u);++m<M;)o.call(this,h=t[m],m)?(d.push([p=+x.call(this,h,m),g=+b.call(this,h,m)]),y.push([+_.call(this,h,m),+w.call(this,h,m)])):d.length&&(l(),d=[],y=[]);return d.length&&l(),v.length?v.join(""):null}var e=Ce,r=Ce,i=0,u=ze,o=zt,a=xu,l=a.key,c=a,f="L",s=.7;return t.x=function(n){return arguments.length?(e=r=n,t):r},t.x0=function(n){return arguments.length?(e=n,t):e},t.x1=function(n){return arguments.length?(r=n,t):r},t.y=function(n){return arguments.length?(i=u=n,t):u},t.y0=function(n){return arguments.length?(i=n,t):i},t.y1=function(n){return arguments.length?(u=n,t):u},t.defined=function(n){return arguments.length?(o=n,t):o},t.interpolate=function(n){return arguments.length?(l="function"==typeof n?a=n:(a=Tl.get(n)||xu).key,c=a.reverse||a,f=a.closed?"M":"L",t):l},t.tension=function(n){return arguments.length?(s=n,t):s},t}function Iu(n){return n.radius}function Yu(n){return[n.x,n.y]}function Zu(n){return function(){var t=n.apply(this,
 arguments),e=t[0],r=t[1]-Io;return[e*Math.cos(r),e*Math.sin(r)]}}function Vu(){return 64}function Xu(){return"circle"}function $u(n){var t=Math.sqrt(n/Fo);return"M0,"+t+"A"+t+","+t+" 0 1,1 0,"+-t+"A"+t+","+t+" 0 1,1 0,"+t+"Z"}function Bu(n){return function(){var t,e,r;(t=this[n])&&(r=t[e=t.active])&&(r.timer.c=null,r.timer.t=NaN,--t.count?delete t[e]:delete this[n],t.active+=.5,r.event&&r.event.interrupt.call(this,this.__data__,r.index))}}function Wu(n,t,e){return ko(n,Yl),n.namespace=t,n.id=e,n}function Ju(n,t,e,r){var i=n.id,u=n.namespace;return Y(n,"function"==typeof e?function(n,o,a){n[u][i].tween.set(t,r(e.call(n,n.__data__,o,a)))}:(e=r(e),function(n){n[u][i].tween.set(t,e)}))}function Gu(n){return null==n&&(n=""),function(){this.textContent=n}}function Ku(n){return null==n?"__transition__":"__transition_"+n+"__"}function Qu(n,t,e,r,i){function u(n){var t=v.delay;return f.t=t+l,n>=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.coun
 t,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro
 (n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]<Kl[u]/i?u-1:u]:[tc,Ki(n,e)[2]]}return r.invert=function(t){return io(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(io)},r.nice=function(n,t){function e(e){return!isNaN(e)&&!n.range(e,io(+e+1),t).length}var u=r.domain(),o=Yi(u),a=null==n?i(o,10):"number"==typeof n&&i(o,n);return a&&(n=a[0],t=a[1]),r.domain(Xi(u,t>1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.respon
 seText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i<u;)if(null!=(r=n[i])&&r>=r){e=r;break}for(;++i<u;)null!=(r=n[i])&&e>r&&(e=r)}e
 lse{for(;++i<u;)if(null!=(r=t.call(n,n[i],i))&&r>=r){e=r;break}for(;++i<u;)null!=(r=t.call(n,n[i],i))&&e>r&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i<u;)if(null!=(r=n[i])&&r>=r){e=r;break}for(;++i<u;)null!=(r=n[i])&&r>e&&(e=r)}else{for(;++i<u;)if(null!=(r=t.call(n,n[i],i))&&r>=r){e=r;break}for(;++i<u;)null!=(r=t.call(n,n[i],i))&&r>e&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u<o;)if(null!=(r=n[u])&&r>=r){e=i=r;break}for(;++u<o;)null!=(r=n[u])&&(e>r&&(e=r),r>i&&(i=r))}else{for(;++u<o;)if(null!=(r=t.call(n,n[u],u))&&r>=r){e=i=r;break}for(;++u<o;)null!=(r=t.call(n,n[u],u))&&(e>r&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o<u;)i(e=+n[o])&&(r+=e);else for(;++o<u;)i(e=+t.call(n,n[o],o))&&(r+=e);return r},ao.mean=function(n,t){var e,u=0,o=n.length,a=-1,l=o;if(1===arguments.length)for(;++a<o;)i(e=r(n[a]))?u+=e:--l;els
 e for(;++a<o;)i(e=r(t.call(n,n[a],a)))?u+=e:--l;return l?u/l:void 0},ao.quantile=function(n,t){var e=(n.length-1)*t+1,r=Math.floor(e),i=+n[r-1],u=e-r;return u?i+u*(n[r]-i):i},ao.median=function(n,t){var u,o=[],a=n.length,l=-1;if(1===arguments.length)for(;++l<a;)i(u=r(n[l]))&&o.push(u);else for(;++l<a;)i(u=r(t.call(n,n[l],l)))&&o.push(u);return o.length?ao.quantile(o.sort(e),.5):void 0},ao.variance=function(n,t){var e,u,o=n.length,a=0,l=0,c=-1,f=0;if(1===arguments.length)for(;++c<o;)i(e=r(n[c]))&&(u=e-a,a+=u/++f,l+=u*(e-a));else for(;++c<o;)i(e=r(t.call(n,n[c],c)))&&(u=e-a,a+=u/++f,l+=u*(e-a));return f>1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+
 t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t<e;)for(var i,u=-1,a=r[t]=new Array(i);++u<i;)a[u]=n[u][t];return r},ao.zip=function(){return ao.transpose(arguments)},ao.keys=function(n){var t=[];for(var e in n)t.push(e);return t},ao.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},ao.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},ao.merge=function(n){for(var t,e,r,i=n.length,u=-1,o=0;++u<i;)o+=n[u].length;for(e=new Array(o);--i>=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u
 ,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)<t;)i.push(r/u);return i},ao.map=function(n,t){var e=new c;if(n instanceof c)n.forEach(function(n,t){e.set(n,t)});else if(Array.isArray(n)){var r,i=-1,u=n.length;if(1===arguments.length)for(;++i<u;)e.set(i,n[i]);else for(;++i<u;)e.set(t.call(n,r=n[i],i),r)}else for(var o in n)e.set(o,n[o]);return e};var bo="__proto__",_o="\x00";l(c,{has:h,get:function(n){return this._[f(n)]},set:function(n,t){return this._[f(n)]=t},remove:p,keys:g,values:function(){var n=[];for(var t in this._)n.push(this._[t]);return n},entries:function(){var n=[];for(var t in this._)n.push({key:s(t),value:this._[t]});return n},size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t),this._[t])}}),ao.nest=function(){function n(t,o,a){if(a>=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p<g;)(h=d.get(l=v(f=o[p])))?h.push(f):d.set(l,[f]);return t?(f=t(),s=function(e,r){f.set(e,n(t,r,a))})
 :(f={},s=function(e,r){f[e]=n(t,r,a)}),d.forEach(s),f}function t(n,e){if(e>=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r<i;)n[e=arguments[r]]=M(n,t,t[e]);return n};var wo=["webkit","ms","moz","Moz","o","O"];ao.dispatch=function(){for(var n=new _,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=w(n);
 return n},_.prototype.on=function(n,t){var e=n.indexOf("."),r="";if(e>=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o<a;){u.push(t=[]),t.parentNode=(r=this[o]).parentN
 ode;for(var l=-1,c=r.length;++l<c;)(i=r[l])?(t.push(e=n.call(i,i.__data__,l,o)),e&&"__data__"in i&&(e.__data__=i.__data__)):t.push(null)}return E(u)},Co.selectAll=function(n){var t,e,r=[];n=C(n);for(var i=-1,u=this.length;++i<u;)for(var o=this[i],a=-1,l=o.length;++a<l;)(e=o[a])&&(r.push(t=co(n.call(e,e.__data__,a,i))),t.parentNode=e);return E(r)};var zo="http://www.w3.org/1999/xhtml",Lo={svg:"http://www.w3.org/2000/svg",xhtml:zo,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};ao.ns={prefix:Lo,qualify:function(n){var t=n.indexOf(":"),e=n;return t>=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(argument
 s.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++i<r;)if(!t.contains(n[i]))return!1}else for(t=e.getAttribute("class");++i<r;)if(!q(n[i]).test(t))return!1;return!0}for(t in n)this.each(R(t,n[t]));return this}return this.each(R(n,t))},Co.style=function(n,e,r){var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.ea
 ch("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++r<o;)(i=n[r])&&(y.has(d=t.call(i,i.__data__,r))?v[r]=i:y.set(d,i),m[r]=d);for(r=-1;++r<s;)(i=y.get(d=t.call(e,u=e[r],r)))?i!==!0&&(p[r]=i,i.__data__=u):g[r]=H(u),y.set(d,!0);for(r=-1;++r<o;)r in m&&y.get(m[r])!==!0&&(v[r]=n[r])}else{for(r=-1;++r<h;)i=n[r],u=e[r],i?(i.__data__=u,p[r]=i):g[r]=H(u);for(;s>r;++r)g[r]=H(e[r]);for(;o>r
 ;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++u<o;)(i=r[u])&&(n[u]=i.__data__);return n}var a=Z([]),l=E([]),f=E([]);if("function"==typeof n)for(;++u<o;)e(r=this[u],n.call(r,r.parentNode.__data__,u));else for(;++u<o;)e(r=this[u],n);return l.enter=function(){return a},l.exit=function(){return f},l},Co.datum=function(n){return arguments.length?this.property("__data__",n):this.property("__data__")},Co.filter=function(n){var t,e,r,i=[];"function"!=typeof n&&(n=O(n));for(var u=0,o=this.length;o>u;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n<t;)for(var e,r=this[n],i=r.length-1,u=r[i];--i>=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=
 I.apply(this,arguments);for(var t=-1,e=this.length;++t<e;)this[t].sort(n);return this.order()},Co.each=function(n){return Y(this,function(t,e,r){n.call(t,t.__data__,e,r)})},Co.call=function(n){var t=co(arguments);return n.apply(t[0]=this,t),this},Co.empty=function(){return!this.node()},Co.node=function(){for(var n=0,t=this.length;t>n;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++a<l;){r=(i=this[a]).update,o.push(t=[]),t.parentNode=i.parentNode;for(var c=-1,f=i.length;++c<f;)(u=i[c])?(t.push(r[c]=e=n.call(i.parentNode,u.__data__,c,a)),e.__data__=u.__data__):t.push(null)}return E(o)},qo.insert=function(n,t){return arguments.length<2&&(t=V(this)),Co.insert.call(this,n,t)},ao.se
 lect=function(t){var e;return"string"==typeof t?(e=[No(t,fo)],e.parentNode=fo.documentElement):(e=[t],e.parentNode=n(t)),E([e])},ao.selectAll=function(n){var t;return"string"==typeof n?(t=co(Eo(n,fo)),t.parentNode=fo.documentElement):(t=co(n),t.parentNode=null),E([t])},Co.on=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return functi
 on(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[
 2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k})
 .map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e
 ,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoo
 m",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.s
 caleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return n
 ew fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,
 aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[09/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.scss
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.scss b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.scss
new file mode 100644
index 0000000..bd69b21
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.scss
@@ -0,0 +1,2135 @@
+/*
+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.
+*/
+
+@import "https://cdn.rawgit.com/angular-ui/bower-ui-grid/master/ui-grid.min.css";
+@import "https://cdn.rawgit.com/mar10/dynatree/master/dist/skin/ui.dynatree.css";
+
+$sep-width: 6px;
+$left-width: 200px;
+$pane-top: 50px;
+#paneSep {
+	background-color: #FFF;
+    position: absolute;
+    top: $pane-top;
+    bottom: 0;
+    left: 200px;
+    width: $sep-width;
+    cursor: e-resize;
+    border-right: 1px solid #CCC;
+}
+
+#paneLeft {
+    position: absolute;
+    top: $pane-top;
+    bottom: 0;
+    left: 0;
+    width: $left-width;
+    overflow: auto;
+}
+
+#paneRight {
+    position: absolute;
+    top: $pane-top;
+    bottom: 0;
+    left: $left-width + $sep-width;
+    right: 0;
+    overflow: hidden;
+    margin: 0 1em;
+}
+
+$topology-left-width: 400px;
+$topology-pane-top: 80px;
+div.topology-container #paneLeft {
+  top: $topology-pane-top;
+  width: $topology-left-width;
+}
+div.topology-container #paneRight {
+  top: $topology-pane-top;
+  left: $topology-left-width + $sep-width;
+}
+div.topology-container #paneSep {
+  top: $topology-pane-top;
+  left: $topology-left-width;
+}
+
+.ui-grid-viewport {
+/*    overflow: auto; */
+}
+div.qdr-attributes span.dynatree-selected a {
+    background-color: #e0e0ff;
+}
+
+.qdr-overview.pane.left span:not(.dynatree-has-children) .dynatree-icon:before,
+.qdr-attributes.pane.left span:not(.dynatree-has-children) .dynatree-icon:before {
+    color: green;
+}
+
+span:not(.dynatree-has-children).address .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address .dynatree-icon:before {
+    content: "\f0ac";
+}
+span:not(.dynatree-has-children).address.mobile .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address.mobile .dynatree-icon:before {
+    content: "\f109";
+}
+span:not(.dynatree-has-children).address.internal.mobile .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address.internal.mobile .dynatree-icon:before {
+    content: "\f0ac";
+}
+span:not(.dynatree-has-children).address.router .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address.router .dynatree-icon:before {
+    content: "\f047";
+}
+
+span.address-link .dynatree-icon:before {
+    content: "\f0ac";
+}
+
+span:not(.dynatree-has-children).connection.external .dynatree-icon:before {
+    content: "\f109";
+}
+span:not(.dynatree-has-children).connection.normal .dynatree-icon:before {
+    content: "\f08e";
+}
+span:not(.dynatree-has-children).connection.external.quiesced .dynatree-icon:before {
+	content: "\f14c";
+	color: red;
+}
+span:not(.dynatree-has-children).connection.inter-router .dynatree-icon:before {
+     content: "\f07e";
+}
+span:not(.dynatree-has-children).no-data .dynatree-icon:before {
+     content: "\f05e";
+     color: red !important;
+}
+span:not(.dynatree-has-children).loading .dynatree-icon:before {
+     content: "\f254";
+}
+span:not(.dynatree-has-children).connector .dynatree-icon:before {
+     content: "\f126";
+}
+span:not(.dynatree-has-children).container .dynatree-icon:before {
+     content: "\f16c";
+}
+span:not(.dynatree-has-children).log .dynatree-icon:before {
+     content: "\f0f6";
+}
+span:not(.dynatree-has-children).router\.node .dynatree-icon:before {
+     content: "\f013";
+}
+span:not(.dynatree-has-children).link.inter-router .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.link.inter-router .dynatree-icon:before{
+     content: "\f07e";
+}
+span:not(.dynatree-has-children).link.endpoint .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.link.endpoint .dynatree-icon:before{
+     content: "\f109";
+}
+span:not(.dynatree-has-children).link.console .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.link.console .dynatree-icon:before {
+     content: "\f108";
+}
+span:not(.dynatree-has-children).listener .dynatree-icon:before {
+     content: "\f025";
+}
+span:not(.dynatree-has-children).connection .dynatree-icon:before {
+     content: "\f07e";
+}
+span:not(.dynatree-has-children).connection.console .dynatree-icon:before {
+     content: "\f108";
+}
+span:not(.dynatree-has-children).waypoint .dynatree-icon:before {
+     content: "\f0ec";
+}
+span:not(.dynatree-has-children).router .dynatree-icon:before {
+     content: "\f047";
+}
+span:not(.dynatree-has-children).fixedAddress .dynatree-icon:before {
+     content: "\f015";
+}
+span:not(.dynatree-has-children).linkRoutePattern .dynatree-icon:before {
+     content: "\f039";
+}
+span:not(.dynatree-has-children).allocator .dynatree-icon:before {
+     content: "\f170";
+}
+
+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.dynatree-expanded button.btn-filter,
+a.dynatree-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;
+}
+
+ul.dynatree-container {
+  background: inherit;
+}
+ul.dynatree-container li {
+  background: inherit;
+}
+
+span.dynatree-icon {
+  position: relative;
+  top: -2px;
+  font-size: 17px;
+}
+
+span:not(.dynatree-has-children) .dynatree-icon:before {
+  font-family: FontAwesome;
+  content: "\f013";
+}
+
+ul.inline,
+ol.inline {
+  margin-left: 0;
+  list-style: none;
+}
+
+ul.inline > li,
+ol.inline > li {
+  display: inline-block;
+  padding-right: 2px;
+  padding-left: 2px;
+}
+
+[class^="dynatree-folder icon-"], [class*=" dynatree-folder icon-"] {
+
+}
+
+[class^="dynatree-folder icon-"]:before, [class*=" dynatree-folder icon-"]:before {
+  font-size: 17px;
+  margin-left: 18px;
+}
+
+
+[class^="dynatree-folder icon-"], [class*=" dynatree-folder icon-"] .dynatree-connector {
+  display: none;
+}
+
+[class^="dynatree-folder icon-"], [class*=" dynatree-folder icon-"] .dynatree-icon {
+  display: none;
+}
+
+ul.dynatree-container {
+  overflow: visible;
+}
+
+ul.dynatree-container {
+  background: inherit;
+}
+ul.dynatree-container li {
+  background: inherit;
+}
+
+i.expandable-indicator {
+  color: #666;
+}
+
+span.dynatree-expander {
+  color: #728271;
+}
+
+span.dynatree-icon {
+  color: #EECA7C;
+}
+span:not(.dynatree-has-children) .dynatree-icon:before {
+  color: gray;
+}
+
+span.dynatree-empty,
+span.dynatree-vline,
+span.dynatree-connector,
+span.dynatree-expander,
+span.dynatree-icon,
+span.dynatree-checkbox,
+span.dynatree-radio,
+span.dynatree-drag-helper-img,
+#dynatree-drop-marker
+{
+    font-family: FontAwesome;
+    font-weight: normal;
+    font-style: normal;
+    display: inline-block;
+    text-decoration: inherit;
+    background-image: none;
+    vertical-align: middle;
+}
+
+.dynatree-checkbox {
+    color: #888888
+}
+
+/* Dynatree checkbox */
+span.dynatree-checkbox:before
+{
+    margin-top: 1px;
+    background-position: 0 0;
+    cursor: pointer;
+    content: "";
+}
+
+span.dynatree-checkbox:before:hover
+{
+    background-position: 0 0;
+    content: "";
+}
+
+.dynatree-selected span.dynatree-checkbox:before
+{
+    margin-top: 1px;
+    background-position: 0 0;
+    cursor: pointer;
+    content: "\f00c";
+}
+
+.dynatree-selected span.dynatree-checkbox:before:hover
+{
+    background-position: 0 0;
+    content: "\f00c";
+}
+
+
+.dynatree-expander {
+    color: #888888
+}
+
+/* Dynatree expander */
+span.dynatree-expander:before
+{
+    margin-top: 1px;
+    background-position: 0 0;
+    cursor: pointer;
+    content: "\f054";
+}
+
+span.dynatree-expander:before:hover
+{
+    background-position: 0 0;
+    content: "\f054";
+}
+
+.dynatree-exp-e span.dynatree-expander:before,  /* Expanded, not delayed, not last sibling */
+.dynatree-exp-ed span.dynatree-expander:before,  /* Expanded, delayed, not last sibling */
+.dynatree-exp-el span.dynatree-expander:before,  /* Expanded, not delayed, last sibling */
+.dynatree-exp-edl span.dynatree-expander:before  /* Expanded, delayed, last sibling */
+{
+    background-position: 0 0;
+    content: "\f078";
+}
+.dynatree-exp-e span.dynatree-expander:before:hover,  /* Expanded, not delayed, not last sibling */
+.dynatree-exp-ed span.dynatree-expander:before:hover,  /* Expanded, delayed, not last sibling */
+.dynatree-exp-el span.dynatree-expander:before:hover,  /* Expanded, not delayed, last sibling */
+.dynatree-exp-edl span.dynatree-expander:before:hover  /* Expanded, delayed, last sibling */
+{
+    background-position: 0 0;
+    content: "\f0da";
+}
+
+/* closed folder */
+.dynatree-ico-cf span.dynatree-icon:before {
+    background-position: 0 0;
+    content: "\f07b";
+}
+
+/* open folder */
+.dynatree-ico-ef span.dynatree-icon:before {
+    background-position: 0 0;
+    content: "\f07c";
+}
+
+span.dynatree-icon:before {
+    background-position: 0px 0px;
+    content: "\f013";
+}
+
+span.dynatree-folder a {
+    font-weight: normal;
+}
+
+div.treeContainer ul.dynatree-container {
+	border: 0px;
+}
+
+#linkFilter {
+	display: none;
+	padding: 0.5em;
+	border: 1px solid grey;
+	background-color: #F0F0F0;
+	position: absolute;
+	z-index: 100;
+	right: 1em;
+}
+
+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;
+}
+
+div.formLine label, div.formLine input {
+	display: inline-block;
+	padding: 0 8px;
+}
+
+.ui-grid {
+  border: 0;
+/*  height: auto; */
+}
+
+
+.ui-grid-viewport {
+/*  height: 100% !important; */
+/*  overflow-x: auto !important; */
+}
+/*
+.ui-grid-row.ui-grid-row-selected > [ui-grid-row] > .ui-grid-cell {
+  background-color: #e0e0ff;
+}
+*/
+.grid:not(.noHighlight) .ui-grid-row:hover .ui-grid-cell-contents {
+	background-color: #EEE;
+}
+
+.ui-grid-top-panel {
+  background: inherit;
+}
+
+/*
+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: #00F;
+	fill: #00F;
+}
+
+path.link {
+  fill: none;
+  stroke: #000;
+  stroke-width: 4px;
+  cursor: default;
+}
+
+svg:not(.active):not(.ctrl) path.link {
+  cursor: pointer;
+}
+
+path.link.selected {
+  stroke-dasharray: 10,2;
+  stroke: #00F  !important;
+}
+
+
+path.link.highlighted {
+    stroke: #0F0 !important;
+
+}
+
+path.link.temp {
+  opacity: 0.3;
+}
+path.link.temp.over {
+  opacity: 0.8;
+  stroke-dasharray: 10,2;
+}
+
+path.link.dragline {
+  pointer-events: none;
+}
+
+path.link.hidden {
+  stroke-width: 0;
+}
+
+
+circle.node {
+    stroke-width: 1.5px;
+    cursor: pointer;
+    stroke: darkgray;
+    fill: lightgray;
+}
+
+circle.node.reflexive {
+    stroke: #F00 !important;
+    stroke-width: 2.5px;
+}
+circle.node.selected {
+    stroke: #F00 !important;
+    stroke-width: 2px;
+    fill: #e0e0ff !important;
+}
+circle.node.inter-router {
+    fill: #EAEAEA;
+}
+circle.node.normal {
+    fill: #F0F000;
+}
+circle.node.on-demand {
+    fill: #C0FFC0;
+}
+circle.node.on-demand.artemis {
+	fill: #FCC;
+	/*opacity: 0.2; */
+}
+
+circle.node.fixed {
+    stroke-dasharray: 10,2;
+}
+circle.node.temp {
+    stroke: #f80;
+    fill: #f0f0ff;
+}
+
+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 .grid {
+  height: inherit !important;
+	min-height: initial !important;
+	overflow: initial;
+}
+*/
+
+#topologyForm {
+  max-height: 20em;
+}
+
+div#multiple_details, div#link_details {
+	height: 300px;
+	width: 700px;
+/*	display: none; */
+  visibility: hidden;
+	padding: 0.5em;
+  border: 1px solid;
+	position: absolute;
+	background-color: white;
+	max-height: 330px !important;
+  overflow: hidden;
+  z-index: 99;
+}
+
+/*
+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;
+}
+
+#topologyForm.selected {
+  border: 1px solid red;
+}
+#topologyForm {
+    border: 1px solid white;
+    padding: 1em 1.5em;
+}
+
+div.qdr-topology.pane.left .ngViewport {
+    /* border: 1px solid lightgray; */
+}
+
+#topologyForm > div {
+  /* width:396px; */
+}
+
+/* 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: 0;
+    left: 0;
+    z-index: 100;
+}
+
+.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: 74px;
+    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.grid {
+	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.dynatree-expanded button.btn-filter,
+a.dynatree-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;}
+}
+
+.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: 24px !important;
+}/*
+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 .gridStyle {
+    width: 20em;
+    margin-right: 0;
+    float: left;
+}
+
+
+.qdrList div.gridDetails {
+    width: auto;
+}
+
+.selectedItems {
+    /* margin-left: 21em; */
+}
+
+.qdrListPane {
+    top: 110px;
+}
+
+.qdrListActions {
+    width: auto;
+}
+
+div.listAttrName {
+    padding-top: 5px;
+}
+
+div.listAttrName i.icon-bar-chart {
+    float: right;
+    margin: 3px 5px;
+}
+
+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; */
+    font-weight: bold;
+}
+
+
+.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;
+}
+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;
+}
+
+#list-controller .tree-header {
+    position: absolute;
+    height: auto;
+}
+
+#list-controller select {
+	height: auto;
+	float: left;
+}
+
+
+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.dynatree-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;
+}
+
+/* the selected row in the name table */
+div#main.qdr div.qdrList div.selected {
+  background-color: #e0e0ff !important;
+}
+
+#dialogChart {
+    height: 200px;
+}
+
+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;
+    margin-left: 2em;
+    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;
+    margin-bottom: 10px;
+}
+
+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;
+}
+
+.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;
+}
+
+.treeContainer {
+	width: 100%;
+	float: left;
+}
+
+.pane-content {
+	overflow: auto;
+}
+
+#entityNames {
+    width: 20em;
+    float: left;
+}
+
+.treeDetails {
+	margin-left: 260px;
+}
+
+.gridStyle:not(.noHighlight) .ui-grid-row:hover .ui-grid-cell-contents {
+	background-color: #e0e0ff;
+}
+
+.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(.dynatree-has-children) .dynatree-icon:before,
+.qdr-attributes.pane.left span:not(.dynatree-has-children) .dynatree-icon:before {
+    color: green;
+}
+
+span:not(.dynatree-has-children).address .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address .dynatree-icon:before {
+    content: "\f0ac";
+}
+span:not(.dynatree-has-children).address.mobile .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address.mobile .dynatree-icon:before {
+    content: "\f109";
+}
+span:not(.dynatree-has-children).address.internal.mobile .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address.internal.mobile .dynatree-icon:before {
+    content: "\f0ac";
+}
+span:not(.dynatree-has-children).address.router .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.address.router .dynatree-icon:before {
+    content: "\f047";
+}
+
+span.address-link .dynatree-icon:before {
+    content: "\f0ac";
+}
+
+span:not(.dynatree-has-children).connection.external .dynatree-icon:before {
+    content: "\f109";
+}
+span:not(.dynatree-has-children).connection.normal .dynatree-icon:before {
+    content: "\f08e";
+}
+span:not(.dynatree-has-children).connection.external.quiesced .dynatree-icon:before {
+	content: "\f14c";
+	color: red;
+}
+span:not(.dynatree-has-children).connection.inter-router .dynatree-icon:before {
+     content: "\f07e";
+}
+span:not(.dynatree-has-children).no-data .dynatree-icon:before {
+     content: "\f05e";
+     color: red !important;
+}
+span:not(.dynatree-has-children).loading .dynatree-icon:before {
+     content: "\f254";
+}
+span:not(.dynatree-has-children).connector .dynatree-icon:before {
+     content: "\f126";
+}
+span:not(.dynatree-has-children).container .dynatree-icon:before {
+     content: "\f16c";
+}
+span:not(.dynatree-has-children).log .dynatree-icon:before {
+     content: "\f0f6";
+}
+span:not(.dynatree-has-children).router\.node .dynatree-icon:before {
+     content: "\f013";
+}
+span:not(.dynatree-has-children).link.inter-router .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.link.inter-router .dynatree-icon:before{
+     content: "\f07e";
+}
+span:not(.dynatree-has-children).link.endpoint .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.link.endpoint .dynatree-icon:before{
+     content: "\f109";
+}
+span:not(.dynatree-has-children).link.console .dynatree-icon:before,
+span:not(.dynatree-has-children).router\.link.console .dynatree-icon:before {
+     content: "\f108";
+}
+span:not(.dynatree-has-children).listener .dynatree-icon:before {
+     content: "\f025";
+}
+span:not(.dynatree-has-children).connection .dynatree-icon:before {
+     content: "\f07e";
+}
+span:not(.dynatree-has-children).connection.console .dynatree-icon:before {
+     content: "\f108";
+}
+span:not(.dynatree-has-children).waypoint .dynatree-icon:before {
+     content: "\f0ec";
+}
+span:not(.dynatree-has-children).router .dynatree-icon:before {
+     content: "\f047";
+}
+span:not(.dynatree-has-children).fixedAddress .dynatree-icon:before {
+     content: "\f015";
+}
+span:not(.dynatree-has-children).linkRoutePattern .dynatree-icon:before {
+     content: "\f039";
+}
+span:not(.dynatree-has-children).allocator .dynatree-icon:before {
+     content: "\f170";
+}
+
+.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;
+}
+
+circle.node.normal.console {
+    fill: lightcyan;
+}
+
+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.on-demand {
+    font-family: Brokers;
+    font-size: 20px;
+    font-weight: bold;
+}
+
+text.qpid-cpp.on-demand {
+    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;
+}
+
+div#list-controller {
+    padding-left: 300px;
+}
+
+div.topology-container #content_body,
+div.topology-container #content_body .container-fluid,
+div.topology-container #content_body .container-fluid .row,
+div.topology-container #content_body .container-fluid .row .col-xs-12 {
+  height: inherit;
+}
+
+#overview-controller {
+  position: relative;
+}
+
+#overview-controller div.grid {
+  padding-left: 1em;
+}
+
+.overview-dropdown {
+  float: left;
+  color: #666;
+}
+
+.overview-dropdown * {
+  color: inherit;
+}
+
+.overview-dropdown.selected,
+ .overview-dropdown.selected1 {
+  color: black;
+}
+
+.overview-dropdown select {
+  border: 1px solid black;
+  background: initial;
+  padding: 0.5em;
+  font-size: small;
+}
+
+#overview_dropdowns, #overview_charts {
+  display: flex;
+  justify-content: space-between;
+}
+
+.dropdown-entity {
+  font-weight: bold;
+}
+
+.link-menu-item label {
+  font-weight: normal;
+  padding-left: 8px;
+}
+
+.link-menu-item input {
+  margin: 0 0 0 4px;
+  position: relative;
+  top: 4px;
+}
+
+#overview_charts .d3Chart {
+    height: 180px;
+    width: 300px;
+    border: 0;
+    /*background-color: #EEE;*/
+    float: left;
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/jquery.dynatree.min.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/jquery.dynatree.min.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/jquery.dynatree.min.js
new file mode 100644
index 0000000..d946469
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/jquery.dynatree.min.js
@@ -0,0 +1,4 @@
+/*! jQuery Dynatree Plugin - v1.2.8 - 2015-07-04
+* https://github.com/mar10/dynatree/
+* Copyright (c) 2015 Martin Wendt; Licensed MIT, GPL */function _log(a){if(_canLog){var b=Array.prototype.slice.apply(arguments,[1]),c=new Date,d=c.getHours()+":"+c.getMinutes()+":"+c.getSeconds()+"."+c.getMilliseconds();b[0]=d+" - "+b[0];try{switch(a){case"info":window.console.info.apply(window.console,b);break;case"warn":window.console.warn.apply(window.console,b);break;default:window.console.log.apply(window.console,b)}}catch(e){window.console?-2146827850===e.number&&window.console.log(b.join(", ")):_canLog=!1}}}function logMsg(){Array.prototype.unshift.apply(arguments,["debug"]),_log.apply(this,arguments)}var _canLog=!0,getDynaTreePersistData=null,DTNodeStatus_Error=-1,DTNodeStatus_Loading=1,DTNodeStatus_Ok=0;!function($){function getDtNodeFromElement(a){return alert("getDtNodeFromElement is deprecated"),$.ui.dynatree.getNode(a)}function noop(){}function offsetString(a){return 0===a?"":a>0?"+"+a:""+a}function _checkBrowser(){function a(a){a=a.toLowerCase();var b=/(chrome)[ \/]([
 \w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}}var b,c;return b=a(navigator.userAgent),c={},b.browser&&(c[b.browser]=!0,c.version=b.version),c.chrome?c.webkit=!0:c.webkit&&(c.safari=!0),c}function versionCompare(a,b){var c,d,e,f=(""+a).split("."),g=(""+b).split("."),h=Math.min(f.length,g.length);for(e=0;h>e;e++)if(c=parseInt(f[e],10),d=parseInt(g[e],10),isNaN(c)&&(c=f[e]),isNaN(d)&&(d=g[e]),c!=d)return c>d?1:d>c?-1:0/0;return f.length===g.length?0:f.length<g.length?-1:1}function _initDragAndDrop(a){var b=a.options.dnd||null;b&&(b.onDragStart||b.onDrop)&&_registerDnd(),b&&b.onDragStart&&a.$tree.draggable({addClasses:!1,appendTo:"body",containment:!1,delay:0,distance:4,revert:b.revert!==!0?!1:function(a){if(logMsg("draggable.revert(), dropped=",a),"boolean"==typeof a)return!a;var b=$.ui.ddmanager&&
 $.ui.ddmanager.current&&$.ui.ddmanager.current.helper,c=b&&b.hasClass("dynatree-drop-reject");return c},scroll:!0,scrollSpeed:7,scrollSensitivity:10,connectToDynatree:!0,helper:function(a){var b=$.ui.dynatree.getNode(a.target);return b?b.tree._onDragEvent("helper",b,null,a,null,null):"<div></div>"},start:function(a,b){var c=b.helper.data("dtSourceNode");return!!c}}),b&&b.onDrop&&a.$tree.droppable({addClasses:!1,tolerance:"pointer",greedy:!1})}var Class={create:function(){return function(){this.initialize.apply(this,arguments)}}},BROWSER=_checkBrowser(),jquerySupports={positionMyOfs:versionCompare($.ui.version,"1.9")>=0},DynaTreeNode=Class.create();DynaTreeNode.prototype={initialize:function(a,b,c){this.parent=a,this.tree=b,"string"==typeof c&&(c={title:c}),c.key=null==c.key?"_"+b._nodeCount++:""+c.key,this.data=$.extend({},$.ui.dynatree.nodedatadefaults,c),this.li=null,this.span=null,this.ul=null,this.childList=null,this._isLoading=!1,this.hasSubSel=!1,this.bExpanded=!1,this.bSelect
 ed=!1},toString:function(){return"DynaTreeNode<"+this.data.key+">: '"+this.data.title+"'"},toDict:function(a,b){var c,d=$.extend({},this.data);if(d.activate=this.tree.activeNode===this,d.focus=this.tree.focusNode===this,d.expand=this.bExpanded,d.select=this.bSelected,b&&b(d),a&&this.childList){d.children=[];for(var e=0,f=this.childList.length;f>e;e++)c=this.childList[e],c.isStatusNode()||d.children.push(c.toDict(!0,b))}else delete d.children;return d},fromDict:function(a){var b=a.children;return void 0===b?(this.data=$.extend(this.data,a),void this.render()):(a=$.extend({},a),a.children=void 0,this.data=$.extend(this.data,a),this.removeChildren(),void this.addChild(b))},_getInnerHtml:function(){var a,b=this.tree,c=b.options,d=b.cache,e=this.getLevel(),f=this.data,g="";e<c.minExpandLevel?e>1&&(g+=d.tagConnector):g+=this.hasChildren()!==!1?d.tagExpander:d.tagConnector,c.checkbox&&f.hideCheckbox!==!0&&!f.isStatusNode&&(g+=d.tagCheckbox),f.icon?(a="/"===f.icon.charAt(0)?f.icon:c.imagePa
 th+f.icon,g+="<img src='"+a+"' alt='' />"):f.icon===!1||(g+=f.iconClass?"<span class=' "+f.iconClass+"'></span>":d.tagNodeIcon);var h="";if(c.onCustomRender&&(h=c.onCustomRender.call(b,this)||""),!h){var i=f.tooltip?' title="'+f.tooltip.replace(/\"/g,"&quot;")+'"':"",j=f.href||"#";h=c.noLink||f.noLink?'<span style="display:inline-block;" class="'+c.classNames.title+'"'+i+">"+f.title+"</span>":'<a href="'+j+'" class="'+c.classNames.title+'"'+i+">"+f.title+"</a>"}return g+=h},_fixOrder:function(){var a=this.childList;if(a&&this.ul)for(var b=this.ul.firstChild,c=0,d=a.length-1;d>c;c++){var e=a[c],f=b.dtnode;e!==f?(this.tree.logDebug("_fixOrder: mismatch at index "+c+": "+e+" != "+f),this.ul.insertBefore(e.li,f.li)):b=b.nextSibling}},render:function(a,b){var c=this.tree,d=this.parent,e=this.data,f=c.options,g=f.classNames,h=this.isLastSibling(),i=!1;if(d||this.ul){if(d){this.li||(i=!0,this.li=document.createElement("li"),this.li.dtnode=this,e.key&&f.generateIds&&(this.li.id=f.idPrefix+e
 .key),this.span=document.createElement("span"),this.span.className=g.title,this.li.appendChild(this.span),d.ul||(d.ul=document.createElement("ul"),d.ul.style.display="none",d.li.appendChild(d.ul)),d.ul.appendChild(this.li)),this.span.innerHTML=this._getInnerHtml();var j=[];j.push(g.node),e.isFolder&&j.push(g.folder),this.bExpanded&&j.push(g.expanded),this.hasChildren()!==!1&&j.push(g.hasChildren),e.isLazy&&null===this.childList&&j.push(g.lazy),h&&j.push(g.lastsib),this.bSelected&&j.push(g.selected),this.hasSubSel&&j.push(g.partsel),c.activeNode===this&&j.push(g.active),e.addClass&&j.push(e.addClass),j.push(g.combinedExpanderPrefix+(this.bExpanded?"e":"c")+(e.isLazy&&null===this.childList?"d":"")+(h?"l":"")),j.push(g.combinedIconPrefix+(this.bExpanded?"e":"c")+(e.isFolder?"f":"")),this.span.className=j.join(" "),this.li.className=h?g.lastsib:"",i&&f.onCreate&&f.onCreate.call(c,this,this.span),f.onRender&&f.onRender.call(c,this,this.span)}}else this.li=this.span=null,this.ul=document.
 createElement("ul"),this.ul.className=f.minExpandLevel>1?g.container+" "+g.noConnector:g.container;if((this.bExpanded||b===!0)&&this.childList){for(var k=0,l=this.childList.length;l>k;k++)this.childList[k].render(!1,b);this._fixOrder()}if(this.ul){var m="none"===this.ul.style.display,n=!!this.bExpanded;if(a&&f.fx&&m===n){var o=f.fx.duration||200;$(this.ul).animate(f.fx,o)}else this.ul.style.display=this.bExpanded||!d?"":"none"}},getKeyPath:function(a){var b=[],c=this.tree.options.keyPathSeparator;return this.visitParents(function(a){a.parent&&b.unshift(a.data.key)},!a),c+b.join(c)},getParent:function(){return this.parent},getChildren:function(){return void 0===this.hasChildren()?void 0:this.childList},hasChildren:function(){return this.data.isLazy?null===this.childList||void 0===this.childList?void 0:0===this.childList.length?!1:1===this.childList.length&&this.childList[0].isStatusNode()?void 0:!0:!!this.childList},isFirstSibling:function(){var a=this.parent;return!a||a.childList[0]
 ===this},isLastSibling:function(){var a=this.parent;return!a||a.childList[a.childList.length-1]===this},isLoading:function(){return!!this._isLoading},getPrevSibling:function(){if(!this.parent)return null;for(var a=this.parent.childList,b=1,c=a.length;c>b;b++)if(a[b]===this)return a[b-1];return null},getNextSibling:function(){if(!this.parent)return null;for(var a=this.parent.childList,b=0,c=a.length-1;c>b;b++)if(a[b]===this)return a[b+1];return null},isStatusNode:function(){return this.data.isStatusNode===!0},isChildOf:function(a){return this.parent&&this.parent===a},isDescendantOf:function(a){if(!a)return!1;for(var b=this.parent;b;){if(b===a)return!0;b=b.parent}return!1},countChildren:function(){var a=this.childList;if(!a)return 0;for(var b=a.length,c=0,d=b;d>c;c++){var e=a[c];b+=e.countChildren()}return b},sortChildren:function(a,b){var c=this.childList;if(c){if(a=a||function(a,b){var c=a.data.title.toLowerCase(),d=b.data.title.toLowerCase();return c===d?0:c>d?1:-1},c.sort(a),b)for
 (var d=0,e=c.length;e>d;d++)c[d].childList&&c[d].sortChildren(a,"$norender$");"$norender$"!==b&&this.render()}},_setStatusNode:function(a){var b=this.childList?this.childList[0]:null;if(a)b?(a.isStatusNode=!0,a.key="_statusNode",b.data=a,b.render()):(a.isStatusNode=!0,a.key="_statusNode",b=this.addChild(a));else if(b&&b.isStatusNode()){try{this.ul&&(this.ul.removeChild(b.li),b.li=null)}catch(c){}1===this.childList.length?this.childList=[]:this.childList.shift()}},setLazyNodeStatus:function(a,b){var c=b&&b.tooltip?b.tooltip:null,d=b&&b.info?" ("+b.info+")":"";switch(a){case DTNodeStatus_Ok:this._setStatusNode(null),$(this.span).removeClass(this.tree.options.classNames.nodeLoading),this._isLoading=!1,this.tree.options.autoFocus&&(this===this.tree.tnRoot&&this.childList&&this.childList.length>0?this.childList[0].focus():this.focus());break;case DTNodeStatus_Loading:this._isLoading=!0,$(this.span).addClass(this.tree.options.classNames.nodeLoading),this.parent||this._setStatusNode({title
 :this.tree.options.strings.loading+d,tooltip:c,addClass:this.tree.options.classNames.nodeWait});break;case DTNodeStatus_Error:this._isLoading=!1,this._setStatusNode({title:this.tree.options.strings.loadError+d,tooltip:c,addClass:this.tree.options.classNames.nodeError});break;default:throw"Bad LazyNodeStatus: '"+a+"'."}},_parentList:function(a,b){for(var c=[],d=b?this:this.parent;d;)(a||d.parent)&&c.unshift(d),d=d.parent;return c},getLevel:function(){for(var a=0,b=this.parent;b;)a++,b=b.parent;return a},_getTypeForOuterNodeEvent:function(a){var b=this.tree.options.classNames,c=a.target;if(c.className.indexOf(b.node)<0)return null;for(var d=a.pageX-c.offsetLeft,e=a.pageY-c.offsetTop,f=0,g=c.childNodes.length;g>f;f++){var h=c.childNodes[f],i=h.offsetLeft-c.offsetLeft,j=h.offsetTop-c.offsetTop,k=h.clientWidth,l=h.clientHeight;if(d>=i&&i+k>=d&&e>=j&&j+l>=e){if(h.className==b.title)return"title";if(h.className==b.expander)return"expander";if(h.className==b.checkbox||h.className==b.radio)r
 eturn"checkbox";if(h.className==b.nodeIcon)return"icon"}}return"prefix"},getEventTargetType:function(a){var b=a&&a.target?a.target.className:"",c=this.tree.options.classNames;return b.indexOf(c.title)>=0?"title":b.indexOf(c.expander)>=0?"expander":b.indexOf(c.checkbox)>=0||b.indexOf(c.radio)>=0?"checkbox":b.indexOf(c.nodeIcon)>=0?"icon":b.indexOf(c.empty)>=0||b.indexOf(c.vline)>=0||b.indexOf(c.connector)>=0?"prefix":b.indexOf(c.node)>=0?this._getTypeForOuterNodeEvent(a):null},isVisible:function(){for(var a=this._parentList(!0,!1),b=0,c=a.length;c>b;b++)if(!a[b].bExpanded)return!1;return!0},makeVisible:function(){for(var a=this._parentList(!0,!1),b=0,c=a.length;c>b;b++)a[b]._expand(!0)},focus:function(){this.makeVisible();try{$(this.span).find(">a").focus()}catch(a){}},isFocused:function(){return this.tree.tnFocused===this},_activate:function(a,b){this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o",a,b,this);var c=this.tree.options;if(!this.data.isStatusNode)if(a){if(b&&c.o
 nQueryActivate&&c.onQueryActivate.call(this.tree,a,this)===!1)return;if(this.tree.activeNode){if(this.tree.activeNode===this)return;this.tree.activeNode.deactivate()}c.activeVisible&&this.makeVisible(),this.tree.activeNode=this,c.persist&&$.cookie(c.cookieId+"-active",this.data.key,c.cookie),this.tree.persistence.activeKey=this.data.key,$(this.span).addClass(c.classNames.active),b&&c.onActivate&&c.onActivate.call(this.tree,this)}else if(this.tree.activeNode===this){if(c.onQueryActivate&&c.onQueryActivate.call(this.tree,!1,this)===!1)return;$(this.span).removeClass(c.classNames.active),c.persist&&$.cookie(c.cookieId+"-active","",c.cookie),this.tree.persistence.activeKey=null,this.tree.activeNode=null,b&&c.onDeactivate&&c.onDeactivate.call(this.tree,this)}},activate:function(){this._activate(!0,!0)},activateSilently:function(){this._activate(!0,!1)},deactivate:function(){this._activate(!1,!0)},isActive:function(){return this.tree.activeNode===this},_userActivate:function(){var a=!0,b=
 !1;if(this.data.isFolder)switch(this.tree.options.clickFolderMode){case 2:a=!1,b=!0;break;case 3:a=b=!0}null===this.parent&&(b=!1),b&&(this.toggleExpand(),this.focus()),a&&this.activate()},_setSubSel:function(a){a?(this.hasSubSel=!0,$(this.span).addClass(this.tree.options.classNames.partsel)):(this.hasSubSel=!1,$(this.span).removeClass(this.tree.options.classNames.partsel))},_updatePartSelectionState:function(){var a;if(!this.hasChildren())return a=this.bSelected&&!this.data.unselectable&&!this.data.isStatusNode,this._setSubSel(!1),a;var b,c,d=this.childList,e=!0,f=!0;for(b=0,c=d.length;c>b;b++){var g=d[b],h=g._updatePartSelectionState();h!==!1&&(f=!1),h!==!0&&(e=!1)}return a=e?!0:f?!1:void 0,this._setSubSel(void 0===a),this.bSelected=a===!0,a},_fixSelectionState:function(){var a,b,c;if(this.bSelected)for(this.visit(function(a){a.parent._setSubSel(!0),a.data.unselectable||a._select(!0,!1,!1)}),a=this.parent;a;){a._setSubSel(!0);var d=!0;for(b=0,c=a.childList.length;c>b;b++){var e=a.
 childList[b];if(!e.bSelected&&!e.data.isStatusNode&&!e.data.unselectable){d=!1;break}}d&&a._select(!0,!1,!1),a=a.parent}else for(this._setSubSel(!1),this.visit(function(a){a._setSubSel(!1),a._select(!1,!1,!1)}),a=this.parent;a;){a._select(!1,!1,!1);var f=!1;for(b=0,c=a.childList.length;c>b;b++)if(a.childList[b].bSelected||a.childList[b].hasSubSel){f=!0;break}a._setSubSel(f),a=a.parent}},_select:function(a,b,c){var d=this.tree.options;this.data.isStatusNode||this.bSelected!==a&&(b&&d.onQuerySelect&&d.onQuerySelect.call(this.tree,a,this)===!1||(1==d.selectMode&&a&&this.tree.visit(function(a){return a.bSelected?(a._select(!1,!1,!1),!1):void 0}),this.bSelected=a,a?(d.persist&&this.tree.persistence.addSelect(this.data.key),$(this.span).addClass(d.classNames.selected),c&&3===d.selectMode&&this._fixSelectionState(),b&&d.onSelect&&d.onSelect.call(this.tree,!0,this)):(d.persist&&this.tree.persistence.clearSelect(this.data.key),$(this.span).removeClass(d.classNames.selected),c&&3===d.selectMo
 de&&this._fixSelectionState(),b&&d.onSelect&&d.onSelect.call(this.tree,!1,this))))},select:function(a){return this.data.unselectable?this.bSelected:this._select(a!==!1,!0,!0)},toggleSelect:function(){return this.select(!this.bSelected)},isSelected:function(){return this.bSelected},isLazy:function(){return!!this.data.isLazy},_loadContent:function(){try{var a=this.tree.options;this.tree.logDebug("_loadContent: start - %o",this),this.setLazyNodeStatus(DTNodeStatus_Loading),!0===a.onLazyRead.call(this.tree,this)&&(this.setLazyNodeStatus(DTNodeStatus_Ok),this.tree.logDebug("_loadContent: succeeded - %o",this))}catch(b){this.tree.logWarning("_loadContent: failed - %o",b),this.setLazyNodeStatus(DTNodeStatus_Error,{tooltip:""+b})}},_expand:function(a,b){if(this.bExpanded===a)return void this.tree.logDebug("dtnode._expand(%o) IGNORED - %o",a,this);this.tree.logDebug("dtnode._expand(%o) - %o",a,this);var c=this.tree.options;if(!a&&this.getLevel()<c.minExpandLevel)return void this.tree.logDebu
 g("dtnode._expand(%o) prevented collapse - %o",a,this);if(!c.onQueryExpand||c.onQueryExpand.call(this.tree,a,this)!==!1){this.bExpanded=a,c.persist&&(a?this.tree.persistence.addExpand(this.data.key):this.tree.persistence.clearExpand(this.data.key));var d=!(this.data.isLazy&&null===this.childList||this._isLoading||b);if(this.render(d),this.bExpanded&&this.parent&&c.autoCollapse)for(var e=this._parentList(!1,!0),f=0,g=e.length;g>f;f++)e[f].collapseSiblings();return c.activeVisible&&this.tree.activeNode&&!this.tree.activeNode.isVisible()&&this.tree.activeNode.deactivate(),a&&this.data.isLazy&&null===this.childList&&!this._isLoading?void this._loadContent():void(c.onExpand&&c.onExpand.call(this.tree,a,this))}},isExpanded:function(){return this.bExpanded},expand:function(a){a=a!==!1,(this.childList||this.data.isLazy||!a)&&(null!==this.parent||a)&&this._expand(a)},scheduleAction:function(a,b){this.tree.timer&&(clearTimeout(this.tree.timer),this.tree.logDebug("clearTimeout(%o)",this.tree.t
 imer));var c=this;switch(a){case"cancel":break;case"expand":this.tree.timer=setTimeout(function(){c.tree.logDebug("setTimeout: trigger expand"),c.expand(!0)},b);break;case"activate":this.tree.timer=setTimeout(function(){c.tree.logDebug("setTimeout: trigger activate"),c.activate()},b);break;default:throw"Invalid mode "+a}this.tree.logDebug("setTimeout(%s, %s): %s",a,b,this.tree.timer)},toggleExpand:function(){this.expand(!this.bExpanded)},collapseSiblings:function(){if(null!==this.parent)for(var a=this.parent.childList,b=0,c=a.length;c>b;b++)a[b]!==this&&a[b].bExpanded&&a[b]._expand(!1)},_onClick:function(a){var b=this.getEventTargetType(a);if("expander"===b)this.toggleExpand(),this.focus();else if("checkbox"===b)this.toggleSelect(),this.focus();else{this._userActivate();var c=this.span.getElementsByTagName("a");if(!c[0])return!0;BROWSER.msie&&parseInt(BROWSER.version,10)<9||c[0].focus()}a.preventDefault()},_onDblClick:function(){},_onKeydown:function(a){var b,c=!0;switch(a.which){ca
 se 107:case 187:this.bExpanded||this.toggleExpand();break;case 109:case 189:this.bExpanded&&this.toggleExpand();break;case 32:this._userActivate();break;case 8:this.parent&&this.parent.focus();break;case 37:this.bExpanded?(this.toggleExpand(),this.focus()):this.parent&&this.parent.parent&&this.parent.focus();break;case 39:this.bExpanded||!this.childList&&!this.data.isLazy?this.childList&&this.childList[0].focus():(this.toggleExpand(),this.focus());break;case 38:for(b=this.getPrevSibling();b&&b.bExpanded&&b.childList;)b=b.childList[b.childList.length-1];!b&&this.parent&&this.parent.parent&&(b=this.parent),b&&b.focus();break;case 40:if(this.bExpanded&&this.childList)b=this.childList[0];else for(var d=this._parentList(!1,!0),e=d.length-1;e>=0&&!(b=d[e].getNextSibling());e--);b&&b.focus();break;default:c=!1}c&&a.preventDefault()},_onKeypress:function(){},_onFocus:function(a){var b=this.tree.options;"blur"==a.type||"focusout"==a.type?(b.onBlur&&b.onBlur.call(this.tree,this),this.tree.tnF
 ocused&&$(this.tree.tnFocused.span).removeClass(b.classNames.focused),this.tree.tnFocused=null,b.persist&&$.cookie(b.cookieId+"-focus","",b.cookie)):("focus"==a.type||"focusin"==a.type)&&(this.tree.tnFocused&&this.tree.tnFocused!==this&&(this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o",this.tree.tnFocused),$(this.tree.tnFocused.span).removeClass(b.classNames.focused)),this.tree.tnFocused=this,b.onFocus&&b.onFocus.call(this.tree,this),$(this.tree.tnFocused.span).addClass(b.classNames.focused),b.persist&&$.cookie(b.cookieId+"-focus",this.data.key,b.cookie))},visit:function(a,b){var c=!0;if(b===!0&&(c=a(this),c===!1||"skip"===c))return c;if(this.childList)for(var d=0,e=this.childList.length;e>d&&(c=this.childList[d].visit(a,!0),c!==!1);d++);return c},visitParents:function(a,b){if(b&&a(this)===!1)return!1;for(var c=this.parent;c;){if(a(c)===!1)return!1;c=c.parent}return!0},remove:function(){if(this===this.tree.root)throw"Cannot remove system root";return this.parent.remove
 Child(this)},removeChild:function(a){var b=this.childList;if(1===b.length){if(a!==b[0])throw"removeChild: invalid child";return this.removeChildren()}a===this.tree.activeNode&&a.deactivate(),this.tree.options.persist&&(a.bSelected&&this.tree.persistence.clearSelect(a.data.key),a.bExpanded&&this.tree.persistence.clearExpand(a.data.key)),a.removeChildren(!0),this.ul&&a.li&&this.ul.removeChild(a.li);for(var c=0,d=b.length;d>c;c++)if(b[c]===a){this.childList.splice(c,1);break}},removeChildren:function(a,b){this.tree.logDebug("%s.removeChildren(%o)",this,a);var c=this.tree,d=this.childList;if(d){for(var e=0,f=d.length;f>e;e++){var g=d[e];g!==c.activeNode||b||g.deactivate(),this.tree.options.persist&&!b&&(g.bSelected&&this.tree.persistence.clearSelect(g.data.key),g.bExpanded&&this.tree.persistence.clearExpand(g.data.key)),g.removeChildren(!0,b),this.ul&&g.li&&$("li",$(this.ul)).remove()}this.childList=null}a||(this._isLoading=!1,this.render())},setTitle:function(a){this.fromDict({title:a}
 )},reload:function(){throw"Use reloadChildren() instead"},reloadChildren:function(a){if(null===this.parent)throw"Use tree.reload() instead";if(!this.data.isLazy)throw"node.reloadChildren() requires lazy nodes.";if(a){var b=this,c="nodeLoaded.dynatree."+this.tree.$tree.attr("id")+"."+this.data.key;this.tree.$tree.bind(c,function(d,e,f){if(b.tree.$tree.unbind(c),b.tree.logDebug("loaded %o, %o, %o",d,e,f),e!==b)throw"got invalid load event";a.call(b.tree,e,f)})}this.removeChildren(),this._loadContent()},_loadKeyPath:function(a,b){var c=this.tree;if(c.logDebug("%s._loadKeyPath(%s)",this,a),""===a)throw"Key path must not be empty";var d=a.split(c.options.keyPathSeparator);if(""===d[0])throw"Key path must be relative (don't start with '/')";var e=d.shift();if(this.childList)for(var f=0,g=this.childList.length;g>f;f++){var h=this.childList[f];if(h.data.key===e){if(0===d.length)b.call(c,h,"ok");else if(!h.data.isLazy||null!==h.childList&&void 0!==h.childList)b.call(c,h,"loaded"),h._loadKeyP
 ath(d.join(c.options.keyPathSeparator),b);else{c.logDebug("%s._loadKeyPath(%s) -> reloading %s...",this,a,h);var i=this;h.reloadChildren(function(e,f){f?(c.logDebug("%s._loadKeyPath(%s) -> reloaded %s.",e,a,e),b.call(c,h,"loaded"),e._loadKeyPath(d.join(c.options.keyPathSeparator),b)):(c.logWarning("%s._loadKeyPath(%s) -> reloadChildren() failed.",i,a),b.call(c,h,"error"))})}return}}b.call(c,void 0,"notfound",e,0===d.length),c.logWarning("Node not found: "+e)},resetLazy:function(){if(null===this.parent)throw"Use tree.reload() instead";if(!this.data.isLazy)throw"node.resetLazy() requires lazy nodes.";this.expand(!1),this.removeChildren()},_addChildNode:function(a,b){var c=this.tree,d=c.options,e=c.persistence;if(a.parent=this,null===this.childList?this.childList=[]:b||this.childList.length>0&&$(this.childList[this.childList.length-1].span).removeClass(d.classNames.lastsib),b){var f=$.inArray(b,this.childList);if(0>f)throw"<beforeNode> must be a child of <this>";this.childList.splice(f
 ,0,a)}else this.childList.push(a);var g=c.isInitializing();if(d.persist&&e.cookiesFound&&g?(e.activeKey===a.data.key&&(c.activeNode=a),e.focusedKey===a.data.key&&(c.focusNode=a),a.bExpanded=$.inArray(a.data.key,e.expandedKeyList)>=0,a.bSelected=$.inArray(a.data.key,e.selectedKeyList)>=0):(a.data.activate&&(c.activeNode=a,d.persist&&(e.activeKey=a.data.key)),a.data.focus&&(c.focusNode=a,d.persist&&(e.focusedKey=a.data.key)),a.bExpanded=a.data.expand===!0,a.bExpanded&&d.persist&&e.addExpand(a.data.key),a.bSelected=a.data.select===!0,a.bSelected&&d.persist&&e.addSelect(a.data.key)),d.minExpandLevel>=a.getLevel()&&(this.bExpanded=!0),a.bSelected&&3==d.selectMode)for(var h=this;h;)h.hasSubSel||h._setSubSel(!0),h=h.parent;return c.bEnableUpdate&&this.render(),a},addChild:function(a,b){if("string"==typeof a)throw"Invalid data type for "+a;if(a&&0!==a.length){if(a instanceof DynaTreeNode)return this._addChildNode(a,b);a.length||(a=[a]);for(var c=this.tree.enableUpdate(!1),d=null,e=0,f=a.len
 gth;f>e;e++){var g=a[e],h=this._addChildNode(new DynaTreeNode(this,this.tree,g),b);d||(d=h),g.children&&h.addChild(g.children,null)}return this.tree.enableUpdate(c),d}},append:function(a){return this.tree.logWarning("node.append() is deprecated (use node.addChild() instead)."),this.addChild(a,null)},appendAjax:function(a){var b=this;if(this.removeChildren(!1,!0),this.setLazyNodeStatus(DTNodeStatus_Loading),a.debugLazyDelay){var c=a.debugLazyDelay;return a.debugLazyDelay=0,this.tree.logInfo("appendAjax: waiting for debugLazyDelay "+c),void setTimeout(function(){b.appendAjax(a)},c)}var d=a.success,e=a.error,f="nodeLoaded.dynatree."+this.tree.$tree.attr("id")+"."+this.data.key,g=$.extend({},this.tree.options.ajaxDefaults,a,{success:function(a,c){var e=b.tree.phase,g=b.tree.options;b.tree.phase="init",g.postProcess?a=g.postProcess.call(this,a,this.dataType):a&&a.hasOwnProperty("d")&&(a="string"==typeof a.d?$.parseJSON(a.d):a.d),$.isArray(a)&&0===a.length||b.addChild(a,null),b.tree.phase
 ="postInit",d&&d.call(g,b,a,c),b.tree.logDebug("trigger "+f),b.tree.$tree.trigger(f,[b,!0]),b.tree.phase=e,b.setLazyNodeStatus(DTNodeStatus_Ok),$.isArray(a)&&0===a.length&&(b.childList=[],b.render())},error:function(a,c,d){b.tree.logWarning("appendAjax failed:",c,":\n",a,"\n",d),e&&e.call(g,b,a,c,d),b.tree.$tree.trigger(f,[b,!1]),b.setLazyNodeStatus(DTNodeStatus_Error,{info:c,tooltip:""+d})}});$.ajax(g)},move:function(a,b){var c;if(this!==a){if(!this.parent)throw"Cannot move system root";(void 0===b||"over"==b)&&(b="child");var d=this.parent,e="child"===b?a:a.parent;if(e.isDescendantOf(this))throw"Cannot move a node to it's own descendant";if(1==this.parent.childList.length)this.parent.childList=this.parent.data.isLazy?[]:null,this.parent.bExpanded=!1;else{if(c=$.inArray(this,this.parent.childList),0>c)throw"Internal error";this.parent.childList.splice(c,1)}if(this.parent.ul&&this.li&&this.parent.ul.removeChild(this.li),this.parent=e,e.hasChildren())switch(b){case"child":e.childList
 .push(this);break;case"before":if(c=$.inArray(a,e.childList),0>c)throw"Internal error";e.childList.splice(c,0,this);break;case"after":if(c=$.inArray(a,e.childList),0>c)throw"Internal error";e.childList.splice(c+1,0,this);break;default:throw"Invalid mode "+b}else e.childList=[this];if(e.ul||(e.ul=document.createElement("ul"),e.ul.style.display="none",e.li&&e.li.appendChild(e.ul)),this.li&&e.ul.appendChild(this.li),this.tree!==a.tree)throw this.visit(function(b){b.tree=a.tree},null,!0),"Not yet implemented.";d.isDescendantOf(e)||d.render(),e.isDescendantOf(d)||e.render()}},lastentry:void 0};var DynaTreeStatus=Class.create();DynaTreeStatus._getTreePersistData=function(a,b){var c=new DynaTreeStatus(a,b);return c.read(),c.toDict()},getDynaTreePersistData=DynaTreeStatus._getTreePersistData,DynaTreeStatus.prototype={initialize:function(a,b){void 0===a&&(a=$.ui.dynatree.prototype.options.cookieId),b=$.extend({},$.ui.dynatree.prototype.options.cookie,b),this.cookieId=a,this.cookieOpts=b,this
 .cookiesFound=void 0,this.activeKey=null,this.focusedKey=null,this.expandedKeyList=null,this.selectedKeyList=null},_log:function(){Array.prototype.unshift.apply(arguments,["debug"]),_log.apply(this,arguments)},read:function(){this.cookiesFound=!1;var a=$.cookie(this.cookieId+"-active");this.activeKey=a||"",a&&(this.cookiesFound=!0),a=$.cookie(this.cookieId+"-focus"),this.focusedKey=a||"",a&&(this.cookiesFound=!0),a=$.cookie(this.cookieId+"-expand"),this.expandedKeyList=a?a.split(","):[],a&&(this.cookiesFound=!0),a=$.cookie(this.cookieId+"-select"),this.selectedKeyList=a?a.split(","):[],a&&(this.cookiesFound=!0)},write:function(){$.cookie(this.cookieId+"-active",null===this.activeKey?"":this.activeKey,this.cookieOpts),$.cookie(this.cookieId+"-focus",null===this.focusedKey?"":this.focusedKey,this.cookieOpts),$.cookie(this.cookieId+"-expand",null===this.expandedKeyList?"":this.expandedKeyList.join(","),this.cookieOpts),$.cookie(this.cookieId+"-select",null===this.selectedKeyList?"":thi
 s.selectedKeyList.join(","),this.cookieOpts)},addExpand:function(a){$.inArray(a,this.expandedKeyList)<0&&(this.expandedKeyList.push(a),$.cookie(this.cookieId+"-expand",this.expandedKeyList.join(","),this.cookieOpts))},clearExpand:function(a){var b=$.inArray(a,this.expandedKeyList);b>=0&&(this.expandedKeyList.splice(b,1),$.cookie(this.cookieId+"-expand",this.expandedKeyList.join(","),this.cookieOpts))},addSelect:function(a){$.inArray(a,this.selectedKeyList)<0&&(this.selectedKeyList.push(a),$.cookie(this.cookieId+"-select",this.selectedKeyList.join(","),this.cookieOpts))},clearSelect:function(a){var b=$.inArray(a,this.selectedKeyList);b>=0&&(this.selectedKeyList.splice(b,1),$.cookie(this.cookieId+"-select",this.selectedKeyList.join(","),this.cookieOpts))},isReloading:function(){return this.cookiesFound===!0},toDict:function(){return{cookiesFound:this.cookiesFound,activeKey:this.activeKey,focusedKey:this.activeKey,expandedKeyList:this.expandedKeyList,selectedKeyList:this.selectedKeyLis
 t}},lastentry:void 0};var DynaTree=Class.create();DynaTree.version="@@Version",DynaTree.prototype={initialize:function(a){this.phase="init",this.$widget=a,this.options=a.options,this.$tree=a.element,this.timer=null,this.divTree=this.$tree.get(0),_initDragAndDrop(this)},_load:function(a){var b=(this.$widget,this.options),c=this;this.bEnableUpdate=!0,this._nodeCount=1,this.activeNode=null,this.focusNode=null,void 0!==b.rootVisible&&this.logWarning("Option 'rootVisible' is no longer supported."),b.minExpandLevel<1&&(this.logWarning("Option 'minExpandLevel' must be >= 1."),b.minExpandLevel=1),b.classNames!==$.ui.dynatree.prototype.options.classNames&&(b.classNames=$.extend({},$.ui.dynatree.prototype.options.classNames,b.classNames)),b.ajaxDefaults!==$.ui.dynatree.prototype.options.ajaxDefaults&&(b.ajaxDefaults=$.extend({},$.ui.dynatree.prototype.options.ajaxDefaults,b.ajaxDefaults)),b.dnd!==$.ui.dynatree.prototype.options.dnd&&(b.dnd=$.extend({},$.ui.dynatree.prototype.options.dnd,b.dnd
 )),b.imagePath||$("script").each(function(){var a=/.*dynatree[^\/]*\.js$/i;return this.src.search(a)>=0?(b.imagePath=this.src.indexOf("/")>=0?this.src.slice(0,this.src.lastIndexOf("/"))+"/skin/":"skin/",c.logDebug("Guessing imagePath from '%s': '%s'",this.src,b.imagePath),!1):void 0}),this.persistence=new DynaTreeStatus(b.cookieId,b.cookie),b.persist&&($.cookie||_log("warn","Please include jquery.cookie.js to use persistence."),this.persistence.read()),this.logDebug("DynaTree.persistence: %o",this.persistence.toDict()),this.cache={tagEmpty:"<span class='"+b.classNames.empty+"'></span>",tagVline:"<span class='"+b.classNames.vline+"'></span>",tagExpander:"<span class='"+b.classNames.expander+"'></span>",tagConnector:"<span class='"+b.classNames.connector+"'></span>",tagNodeIcon:"<span class='"+b.classNames.nodeIcon+"'></span>",tagCheckbox:"<span class='"+b.classNames.checkbox+"'></span>",lastentry:void 0},(b.children||b.initAjax&&b.initAjax.url||b.initId)&&$(this.divTree).empty();var 
 d=this.$tree.find(">ul:first").hide();this.tnRoot=new DynaTreeNode(null,this,{}),this.tnRoot.bExpanded=!0,this.tnRoot.render(),this.divTree.appendChild(this.tnRoot.ul);var e=this.tnRoot,f=b.persist&&this.persistence.isReloading(),g=!1,h=this.enableUpdate(!1);this.logDebug("Dynatree._load(): read tree structure..."),b.children?e.addChild(b.children):b.initAjax&&b.initAjax.url?(g=!0,e.data.isLazy=!0,this._reloadAjax(a)):b.initId?this._createFromTag(e,$("#"+b.initId)):(this._createFromTag(e,d),d.remove()),this._checkConsistency(),g||3!=b.selectMode||e._updatePartSelectionState(),this.logDebug("Dynatree._load(): render nodes..."),this.enableUpdate(h),this.logDebug("Dynatree._load(): bind events..."),this.$widget.bind(),this.logDebug("Dynatree._load(): postInit..."),this.phase="postInit",b.persist&&this.persistence.write(),this.focusNode&&this.focusNode.isVisible()&&(this.logDebug("Focus on init: %o",this.focusNode),this.focusNode.focus()),g||(b.onPostInit&&b.onPostInit.call(this,f,!1),a
 &&a.call(this,"ok")),this.phase="idle"},_reloadAjax:function(a){var b=this.options;if(!b.initAjax||!b.initAjax.url)throw"tree.reload() requires 'initAjax' mode.";var c=this.persistence,d=$.extend({},b.initAjax);d.addActiveKey&&(d.data.activeKey=c.activeKey),d.addFocusedKey&&(d.data.focusedKey=c.focusedKey),d.addExpandedKeyList&&(d.data.expandedKeyList=c.expandedKeyList.join(",")),d.addSelectedKeyList&&(d.data.selectedKeyList=c.selectedKeyList.join(",")),d.success&&this.logWarning("initAjax: success callback is ignored; use onPostInit instead."),d.error&&this.logWarning("initAjax: error callback is ignored; use onPostInit instead.");var e=c.isReloading();d.success=function(c){3==b.selectMode&&c.tree.tnRoot._updatePartSelectionState(),b.onPostInit&&b.onPostInit.call(c.tree,e,!1),a&&a.call(c.tree,"ok")},d.error=function(c,d,f,g){b.onPostInit&&b.onPostInit.call(c.tree,e,!0,d,f,g),a&&a.call(c.tree,"error",d,f,g)},this.logDebug("Dynatree._init(): send Ajax request..."),this.tnRoot.appendA
 jax(d)},toString:function(){return"Dynatree '"+this.$tree.attr("id")+"'"},toDict:function(a){var b=this.tnRoot.toDict(!0);return a?b:b.children},serializeArray:function(a){
+for(var b=this.getSelectedNodes(a),c=this.$tree.attr("name")||this.$tree.attr("id"),d=[],e=0,f=b.length;f>e;e++)d.push({name:c,value:b[e].data.key});return d},getPersistData:function(){return this.persistence.toDict()},logDebug:function(){this.options.debugLevel>=2&&(Array.prototype.unshift.apply(arguments,["debug"]),_log.apply(this,arguments))},logInfo:function(){this.options.debugLevel>=1&&(Array.prototype.unshift.apply(arguments,["info"]),_log.apply(this,arguments))},logWarning:function(){Array.prototype.unshift.apply(arguments,["warn"]),_log.apply(this,arguments)},isInitializing:function(){return"init"==this.phase||"postInit"==this.phase},isReloading:function(){return("init"==this.phase||"postInit"==this.phase)&&this.options.persist&&this.persistence.cookiesFound},isUserEvent:function(){return"userEvent"==this.phase},redraw:function(){this.tnRoot.render(!1,!1)},renderInvisibleNodes:function(){this.tnRoot.render(!1,!0)},reload:function(a){this._load(a)},getRoot:function(){return 
 this.tnRoot},enable:function(){this.$widget.enable()},disable:function(){this.$widget.disable()},getNodeByKey:function(a){var b=document.getElementById(this.options.idPrefix+a);if(b)return b.dtnode?b.dtnode:null;var c=null;return this.visit(function(b){return b.data.key===a?(c=b,!1):void 0},!0),c},getActiveNode:function(){return this.activeNode},reactivate:function(a){var b=this.activeNode;b&&(this.activeNode=null,b.activate(),a&&b.focus())},getSelectedNodes:function(a){var b=[];return this.tnRoot.visit(function(c){return c.bSelected&&(b.push(c),a===!0)?"skip":void 0}),b},activateKey:function(a){var b=null===a?null:this.getNodeByKey(a);return b?(b.focus(),b.activate(),b):(this.activeNode&&this.activeNode.deactivate(),this.activeNode=null,null)},loadKeyPath:function(a,b){var c=a.split(this.options.keyPathSeparator);return""===c[0]&&c.shift(),c[0]==this.tnRoot.data.key&&(this.logDebug("Removed leading root key."),c.shift()),a=c.join(this.options.keyPathSeparator),this.tnRoot._loadKeyP
 ath(a,b)},selectKey:function(a,b){var c=this.getNodeByKey(a);return c?(c.select(b),c):null},enableUpdate:function(a){return this.bEnableUpdate==a?a:(this.bEnableUpdate=a,a&&this.redraw(),!a)},count:function(){return this.tnRoot.countChildren()},visit:function(a,b){return this.tnRoot.visit(a,b)},_createFromTag:function(parentTreeNode,$ulParent){var self=this;$ulParent.find(">li").each(function(){var $li=$(this),$liSpan=$li.find(">span:first"),$liA=$li.find(">a:first"),title,href=null,target=null,tooltip;if($liSpan.length)title=$liSpan.html();else if($liA.length)title=$liA.html(),href=$liA.attr("href"),target=$liA.attr("target"),tooltip=$liA.attr("title");else{title=$li.html();var iPos=title.search(/<ul/i);title=$.trim(iPos>=0?title.substring(0,iPos):title)}var data={title:title,tooltip:tooltip,isFolder:$li.hasClass("folder"),isLazy:$li.hasClass("lazy"),expand:$li.hasClass("expanded"),select:$li.hasClass("selected"),activate:$li.hasClass("active"),focus:$li.hasClass("focused"),noLink:
 $li.hasClass("noLink")};if(href&&(data.href=href,data.target=target),$li.attr("title")&&(data.tooltip=$li.attr("title")),$li.attr("id")&&(data.key=""+$li.attr("id")),$li.attr("data")){var dataAttr=$.trim($li.attr("data"));if(dataAttr){"{"!=dataAttr.charAt(0)&&(dataAttr="{"+dataAttr+"}");try{$.extend(data,eval("("+dataAttr+")"))}catch(e){throw"Error parsing node data: "+e+"\ndata:\n'"+dataAttr+"'"}}}var childNode=parentTreeNode.addChild(data),$ul=$li.find(">ul:first");$ul.length&&self._createFromTag(childNode,$ul)})},_checkConsistency:function(){},_setDndStatus:function(a,b,c,d,e){var f,g=a?$(a.span):null,h=$(b.span),i=0,j="center";if(this.$dndMarker||(this.$dndMarker=$("<div id='dynatree-drop-marker'></div>").hide().css({"z-index":1e3}).prependTo($(this.divTree).parent())),"after"===d||"before"===d||"over"===d){switch(d){case"before":this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-over"),this.$dndMarker.addClass("dynatree-drop-before"),j="top";break;case"after":this.$
 dndMarker.removeClass("dynatree-drop-before dynatree-drop-over"),this.$dndMarker.addClass("dynatree-drop-after"),j="bottom";break;default:this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-before"),this.$dndMarker.addClass("dynatree-drop-over"),h.addClass("dynatree-drop-target"),i=8}f=jquerySupports.positionMyOfs?{my:"left"+offsetString(i)+" center",at:"left "+j,of:h}:{my:"left center",at:"left "+j,of:h,offset:""+i+" 0"},this.$dndMarker.show().position(f)}else h.removeClass("dynatree-drop-target"),this.$dndMarker.hide();"after"===d?h.addClass("dynatree-drop-after"):h.removeClass("dynatree-drop-after"),"before"===d?h.addClass("dynatree-drop-before"):h.removeClass("dynatree-drop-before"),e===!0?(g&&g.addClass("dynatree-drop-accept"),h.addClass("dynatree-drop-accept"),c.addClass("dynatree-drop-accept")):(g&&g.removeClass("dynatree-drop-accept"),h.removeClass("dynatree-drop-accept"),c.removeClass("dynatree-drop-accept")),e===!1?(g&&g.addClass("dynatree-drop-reject"),h.addCla
 ss("dynatree-drop-reject"),c.addClass("dynatree-drop-reject")):(g&&g.removeClass("dynatree-drop-reject"),h.removeClass("dynatree-drop-reject"),c.removeClass("dynatree-drop-reject"))},_onDragEvent:function(a,b,c,d,e,f){var g,h,i,j=this.options.dnd,k=null,l=$(b.span);switch(a){case"helper":var m=$("<div class='dynatree-drag-helper'><span class='dynatree-drag-helper-img' /></div>").append(l.find(".dynatree-title").clone());$("ul.dynatree-container",b.tree.divTree).append(m),m.data("dtSourceNode",b),k=m;break;case"start":b.isStatusNode()?k=!1:j.onDragStart&&(k=j.onDragStart(b)),k===!1?(this.logDebug("tree.onDragStart() cancelled"),e.helper.trigger("mouseup"),e.helper.hide()):l.addClass("dynatree-drag-source");break;case"enter":i=j.onDragEnter?j.onDragEnter(b,c,e,f):null,k=i?$.isArray(i)?{over:$.inArray("over",i)>=0,before:$.inArray("before",i)>=0,after:$.inArray("after",i)>=0}:{over:i===!0||"over"===i,before:i===!0||"before"===i,after:i===!0||"after"===i}:!1,e.helper.data("enterResponse
 ",k);break;case"over":if(h=e.helper.data("enterResponse"),g=null,h===!1);else if("string"==typeof h)g=h;else{var n=l.offset(),o={x:d.pageX-n.left,y:d.pageY-n.top},p={x:o.x/l.width(),y:o.y/l.height()};h.after&&p.y>.75?g="after":!h.over&&h.after&&p.y>.5?g="after":h.before&&p.y<=.25?g="before":!h.over&&h.before&&p.y<=.5?g="before":h.over&&(g="over"),j.preventVoidMoves&&(b===c?g=null:"before"===g&&c&&b===c.getNextSibling()?g=null:"after"===g&&c&&b===c.getPrevSibling()?g=null:"over"===g&&c&&c.parent===b&&c.isLastSibling()&&(g=null)),e.helper.data("hitMode",g)}"over"===g&&j.autoExpandMS&&b.hasChildren()!==!1&&!b.bExpanded&&b.scheduleAction("expand",j.autoExpandMS),g&&j.onDragOver&&(k=j.onDragOver(b,c,g,e,f),("over"===k||"before"===k||"after"===k)&&(g=k)),this._setDndStatus(c,b,e.helper,g,k!==!1&&null!==g);break;case"drop":var q=e.helper.hasClass("dynatree-drop-reject");g=e.helper.data("hitMode"),g&&j.onDrop&&!q&&j.onDrop(b,c,g,e,f);break;case"leave":b.scheduleAction("cancel"),e.helper.dat
 a("enterResponse",null),e.helper.data("hitMode",null),this._setDndStatus(c,b,e.helper,"out",void 0),j.onDragLeave&&j.onDragLeave(b,c,e,f);break;case"stop":l.removeClass("dynatree-drag-source"),j.onDragStop&&j.onDragStop(b);break;default:throw"Unsupported drag event: "+a}return k},cancelDrag:function(){var a=$.ui.ddmanager.current;a&&a.cancel()},lastentry:void 0},$.widget("ui.dynatree",{_init:function(){return versionCompare($.ui.version,"1.8")<0?(this.options.debugLevel>=0&&_log("warn","ui.dynatree._init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher."),this._create()):void(this.options.debugLevel>=2&&_log("debug","ui.dynatree._init() was called; no current default functionality."))},_create:function(){var a=this.options;a.debugLevel>=1&&logMsg("Dynatree._create(): version='%s', debugLevel=%o.",$.ui.dynatree.version,this.options.debugLevel),this.options.event+=".dynatree";this.element.get(0);this.tree=new DynaTree(this),this.tree._load(),this.tree.logDebug("Dyn
 atree._init(): done.")},bind:function(){function a(a){a=$.event.fix(a||window.event);var b=$.ui.dynatree.getNode(a.target);return b?b._onFocus(a):!1}this.unbind();var b="click.dynatree dblclick.dynatree";this.options.keyboard&&(b+=" keypress.dynatree keydown.dynatree"),this.element.bind(b,function(a){var b=$.ui.dynatree.getNode(a.target);if(!b)return!0;var c=b.tree,d=c.options;c.logDebug("event(%s): dtnode: %s",a.type,b);var e=c.phase;c.phase="userEvent";try{switch(a.type){case"click":return d.onClick&&d.onClick.call(c,b,a)===!1?!1:b._onClick(a);case"dblclick":return d.onDblClick&&d.onDblClick.call(c,b,a)===!1?!1:b._onDblClick(a);case"keydown":return d.onKeydown&&d.onKeydown.call(c,b,a)===!1?!1:b._onKeydown(a);case"keypress":return d.onKeypress&&d.onKeypress.call(c,b,a)===!1?!1:b._onKeypress(a)}}catch(f){c.logWarning("bind(%o): dtnode: %o, error: %o",a,b,f)}finally{c.phase=e}});var c=this.tree.divTree;c.addEventListener?(c.addEventListener("focus",a,!0),c.addEventListener("blur",a,!
 0)):c.onfocusin=c.onfocusout=a},unbind:function(){this.element.unbind(".dynatree")},enable:function(){this.bind(),$.Widget.prototype.enable.apply(this,arguments)},disable:function(){this.unbind(),$.Widget.prototype.disable.apply(this,arguments)},getTree:function(){return this.tree},getRoot:function(){return this.tree.getRoot()},getActiveNode:function(){return this.tree.getActiveNode()},getSelectedNodes:function(){return this.tree.getSelectedNodes()},lastentry:void 0}),versionCompare($.ui.version,"1.8")<0&&($.ui.dynatree.getter="getTree getRoot getActiveNode getSelectedNodes"),$.extend($.ui.dynatree,{version:"1.2.8",buildType:"release",_DynaTreeClass:DynaTree,_DynaTreeNodeClass:DynaTreeNode,getNode:function(a){if(a instanceof DynaTreeNode)return a;for(void 0!==a.selector&&(a=a[0]);a;){if(a.dtnode)return a.dtnode;a=a.parentNode}return null},getPersistData:DynaTreeStatus._getTreePersistData}),$.ui.dynatree.prototype.options={title:"Dynatree",minExpandLevel:1,imagePath:null,children:nul
 l,initId:null,initAjax:null,autoFocus:!0,keyboard:!0,persist:!1,autoCollapse:!1,clickFolderMode:3,activeVisible:!0,checkbox:!1,selectMode:2,fx:null,noLink:!1,onClick:null,onDblClick:null,onKeydown:null,onKeypress:null,onFocus:null,onBlur:null,onQueryActivate:null,onQuerySelect:null,onQueryExpand:null,onPostInit:null,onActivate:null,onDeactivate:null,onSelect:null,onExpand:null,onLazyRead:null,onCustomRender:null,onCreate:null,onRender:null,postProcess:null,dnd:{onDragStart:null,onDragStop:null,revert:!1,autoExpandMS:1e3,preventVoidMoves:!0,onDragEnter:null,onDragOver:null,onDrop:null,onDragLeave:null},ajaxDefaults:{cache:!1,timeout:0,dataType:"json"},strings:{loading:"Loading&#8230;",loadError:"Load error!"},generateIds:!1,idPrefix:"dynatree-id-",keyPathSeparator:"/",cookieId:"dynatree",cookie:{expires:null},classNames:{container:"dynatree-container",node:"dynatree-node",folder:"dynatree-folder",empty:"dynatree-empty",vline:"dynatree-vline",expander:"dynatree-expander",connector:"dy
 natree-connector",checkbox:"dynatree-checkbox",radio:"dynatree-radio",nodeIcon:"dynatree-icon",title:"dynatree-title",noConnector:"dynatree-no-connector",nodeError:"dynatree-statusnode-error",nodeWait:"dynatree-statusnode-wait",hidden:"dynatree-hidden",combinedExpanderPrefix:"dynatree-exp-",combinedIconPrefix:"dynatree-ico-",nodeLoading:"dynatree-loading",hasChildren:"dynatree-has-children",active:"dynatree-active",selected:"dynatree-selected",expanded:"dynatree-expanded",lazy:"dynatree-lazy",focused:"dynatree-focused",partsel:"dynatree-partsel",lastsib:"dynatree-lastsib"},debugLevel:1,lastentry:void 0},versionCompare($.ui.version,"1.8")<0&&($.ui.dynatree.defaults=$.ui.dynatree.prototype.options),$.ui.dynatree.nodedatadefaults={title:null,key:null,isFolder:!1,isLazy:!1,tooltip:null,href:null,icon:null,addClass:null,noLink:!1,activate:!1,focus:!1,expand:!1,select:!1,hideCheckbox:!1,unselectable:!1,children:null,lastentry:void 0};var didRegisterDnd=!1,_registerDnd=function(){didRegist
 erDnd||($.ui.plugin.add("draggable","connectToDynatree",{start:function(a,b){var c=$(this).data("ui-draggable")||$(this).data("draggable"),d=b.helper.data("dtSourceNode")||null;return d?(c.offset.click.top=-2,c.offset.click.left=16,d.tree._onDragEvent("start",d,null,a,b,c)):void 0},drag:function(a,b){var c=$(this).data("ui-draggable")||$(this).data("draggable"),d=b.helper.data("dtSourceNode")||null,e=b.helper.data("dtTargetNode")||null,f=$.ui.dynatree.getNode(a.target);if(a.target&&!f){var g=$(a.target).closest("div.dynatree-drag-helper,#dynatree-drop-marker").length>0;if(g)return}b.helper.data("dtTargetNode",f),e&&e!==f&&e.tree._onDragEvent("leave",e,d,a,b,c),f&&f.tree.options.dnd.onDrop&&(f===e?f.tree._onDragEvent("over",f,d,a,b,c):f.tree._onDragEvent("enter",f,d,a,b,c))},stop:function(a,b){var c=$(this).data("ui-draggable")||$(this).data("draggable"),d=b.helper.data("dtSourceNode")||null,e=b.helper.data("dtTargetNode")||null,f=a.type,g="mouseup"==f&&1==a.which;logMsg("draggable-c
 onnectToDynatree.stop: targetNode(from event): %s, dtTargetNode: %s",e,b.helper.data("dtTargetNode")),g||logMsg("Drag was cancelled"),e&&(g&&e.tree._onDragEvent("drop",e,d,a,b,c),e.tree._onDragEvent("leave",e,d,a,b,c)),d&&d.tree._onDragEvent("stop",d,null,a,b,c)}}),didRegisterDnd=!0)}}(jQuery);
\ 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


[02/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.controller.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.controller.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.controller.js
new file mode 100644
index 0000000..a500cea
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.controller.js
@@ -0,0 +1,1703 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+  'use strict';
+
+  angular
+    .module('horizon.dashboard.dispatch.topology')
+    .controller('horizon.dashboard.dispatch.topology.TopologyController', TopologyController);
+
+  TopologyController.$inject = [
+    '$scope',
+    '$rootScope',
+    'horizon.dashboard.dispatch.comService',
+    '$location',
+    '$timeout',
+    '$modal',
+  ]
+
+  var mouseX, mouseY;
+  var dontHide = false;
+  function hideLinkDetails() {
+    d3.select("#link_details").transition()
+      .duration(500)
+      .style("opacity", 0)
+      .each("end", function (d) {
+          d3.select("#link_details").style("visibility", "hidden")
+      })
+  }
+
+  function TopologyController(
+    $scope,
+    $rootScope,
+    QDRService,
+    $location,
+    $timeout,
+    $modal) {
+
+    var ctrl = this;
+    QDRService.addConnectAction( function () {
+      Topology($scope, $rootScope, QDRService, $location, $timeout, $modal)
+    })
+    QDRService.loadConnectOptions(QDRService.connect);
+
+    $scope.multiData = []
+    $scope.multiDetails = {
+      data: 'multiData',
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      enableVerticalScrollbar: 0,
+      enableHorizontalScrollbar: 0,
+      onRegisterApi: function(gridApi){
+        gridApi.selection.on.rowSelectionChanged($scope, function(row){
+          var detailsDiv = d3.select('#link_details')
+          var isVis = detailsDiv.style('visibility') === 'visible';
+          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;
+        var visibleLen = Math.min(obj.entity.linkData.length, 10)
+        var left = parseInt(d3.select('#multiple_details').style("left"))
+        var bounds = $("#topology").position()
+        var detailsDiv = d3.select('#link_details')
+        detailsDiv
+          .style({
+            visibility: 'visible',
+            opacity: 1,
+            left: (left + 20) + "px",
+            top:  (mouseY + 40 - bounds.top + $(document).scrollTop()) + "px",
+            height: ((visibleLen + 1) * 30) + 40 + "px", // +1 for the header row
+            'overflow-y': obj.entity.linkData > 10 ? 'scroll' : 'hidden'})
+      },
+      columnDefs: [
+      {
+        field: 'host',
+        displayName: 'Connection host'
+      },
+      {
+        field: 'user',
+        displayName: 'User'
+      },
+      {
+        field: 'properties',
+        displayName: 'Properties'
+      },
+/*
+      {
+        cellClass: 'gridCellButton',
+        cellTemplate: '<button title="{{quiesceText(row)}} the links" type="button" ng-class="quiesceClass(row)" class="btn" ng-click="$event.stopPropagation();quiesceConnection(row)" ng-disabled="quiesceDisabled(row)">{{quiesceText(row)}}</button>'
+      },
+*/
+      ]
+    };
+    $scope.linkData = [];
+    $scope.linkDetails = {
+      data: 'linkData',
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      enableVerticalScrollbar: 0,
+      enableHorizontalScrollbar: 0,
+      columnDefs: [
+      {
+        field: 'adminStatus',
+        displayName: 'Admin state'
+      },
+      {
+        field: 'operStatus',
+        displayName: 'Oper state'
+      },
+      {
+        field: 'dir',
+        displayName: 'dir'
+      },
+      {
+        field: 'owningAddr',
+        displayName: 'Address'
+      },
+      {
+        field: 'deliveryCount',
+        displayName: 'Delivered',
+        cellClass: 'grid-values'
+
+      },
+      {
+        field: 'uncounts',
+        displayName: 'Outstanding',
+        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>'
+      }*/
+      ]
+    }
+  }
+
+  function Topology(
+    $scope,
+    $rootScope,
+    QDRService,
+    $location,
+    $timeout,
+    $modal) {
+
+    $scope.quiesceState = {}
+    $scope.quiesceConnection = function (row) {
+      // call method to set adminStatus
+    }
+    $scope.quiesceDisabled = function (row) {
+      return false;
+    }
+    $scope.quiesceText = function (row) {
+      return 'Quiesce'
+    }
+    $scope.quiesceClass = function (row) {
+      var stateClassMap = {
+        enabled: 'btn-primary',
+        quiescing: 'btn-warning',
+        reviving: 'btn-warning',
+        quiesced: 'btn-danger'
+      }
+      return 'btn-primary'
+    }
+    $scope.quiesceLinkClass = function (row) {
+      var stateClassMap = {
+        enabled: 'btn-primary',
+        disabled: 'btn-danger'
+      }
+      return stateClassMap[row.entity.adminStatus]
+    }
+    $scope.quiesceLink = function (row) {
+      QDRService.quiesceLink(row.entity.nodeId, row.entity.name);
+    }
+    $scope.quiesceLinkDisabled = function (row) {
+      return (row.entity.operStatus !== 'up' && row.entity.operStatus !== 'down')
+    }
+    $scope.quiesceLinkText = function (row) {
+      return row.entity.operStatus === 'down' ? "Revive" : "Quiesce";
+    }
+
+    // we are currently connected. setup a handler to get notified if we are ever disconnected
+    QDRService.addDisconnectAction( function () {
+      QDR.log.debug("disconnected from router. show a toast message");
+    })
+
+    var urlPrefix = $location.absUrl();
+    urlPrefix = urlPrefix.split("#")[0]
+    QDR.log.debug("started QDR.TopologyController with urlPrefix: " + urlPrefix);
+
+    $scope.addingNode = {
+      step: 0,
+      hasLink: false,
+      trigger: ''
+    };
+
+    $scope.cancel = function () {
+      $scope.addingNode.step = 0;
+    }
+    $scope.editNewRouter = function () {
+      $scope.addingNode.trigger = 'editNode';
+    }
+
+    var NewRouterName = "__NEW__";
+      // mouse event vars
+    var selected_node = null,
+      selected_link = null,
+      mousedown_link = null,
+      mousedown_node = null,
+      mouseup_node = null,
+      initial_mouse_down_position = null;
+
+    $scope.schema = "Not connected";
+
+    $scope.modes = [
+      {title: 'Topology view', name: 'Diagram', right: false},
+      /* {title: 'Add a new router node', name: 'Add Router', right: true} */
+    ];
+    $scope.mode = "Diagram";
+    $scope.contextNode = null; // node that is associated with the current context menu
+
+    $scope.isModeActive = function (name) {
+      if ((name == 'Add Router' || name == 'Diagram') && $scope.addingNode.step > 0)
+        return true;
+      return ($scope.mode == name);
+    }
+    $scope.selectMode = function (name) {
+      if (name == "Add Router") {
+        name = 'Diagram';
+        if ($scope.addingNode.step > 0) {
+          $scope.addingNode.step = 0;
+        } else {
+          // start adding node mode
+          $scope.addingNode.step = 1;
+        }
+      } else {
+        $scope.addingNode.step = 0;
+      }
+
+      $scope.mode = name;
+    }
+    $scope.$watch(function () {return $scope.addingNode.step}, function (newValue, oldValue) {
+      if (newValue == 0 && oldValue != 0) {
+        // we are cancelling the add
+
+        // find the New node
+        nodes.every(function (n, i) {
+          // for the placeholder node, the key will be __internal__
+          if (QDRService.nameFromId(n.key) == '__internal__') {
+            var newLinks = links.filter(function (e, i) {
+              return e.source.id == n.id || e.target.id == n.id;
+            })
+            // newLinks is an array of links to remove
+            newLinks.map(function (e) {
+              links.splice(links.indexOf(e), 1);
+            })
+            // i is the index of the node to remove
+            nodes.splice(i, 1);
+            force.nodes(nodes).links(links).start();
+                    restart(false);
+            return false; // stop looping
+          }
+          return true;
+        })
+        updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0);
+
+      } else if (newValue > 0) {
+        // we are starting the add mode
+        $scope.$broadcast('showAddForm')
+
+        resetMouseVars();
+        selected_node = null;
+        selected_link = null;
+        // add a new node
+        var id = "amqp:/_topo/0/__internal__/$management";
+        var x = radiusNormal * 4;
+        var y = x;;
+        if (newValue > 1) {   // add at current mouse position
+          var offset = jQuery('#topology').offset();
+          x = mouseX - offset.left + $(document).scrollLeft();
+          y = mouseY - offset.top + $(document).scrollTop();;
+        }
+        NewRouterName = genNewName();
+        nodes.push( aNode(id, NewRouterName, "inter-router", undefined, nodes.length, x, y, undefined, true) );
+        force.nodes(nodes).links(links).start();
+        restart(false);
+      }
+    })
+
+    $scope.isRight = function (mode) {
+      return mode.right;
+    }
+
+    // for ng-grid that shows details for multiple consoles/clients
+    // generate unique name for router and containerName
+    var genNewName = function () {
+      var nodeInfo = QDRService.topology.nodeInfo();
+      var nameIndex = 1;
+      var newName = "R." + nameIndex;
+
+      var names = [];
+      for (var key in nodeInfo) {
+        var node = nodeInfo[key];
+        var router = node['.router'];
+        var attrNames = router.attributeNames;
+        var name = QDRService.valFor(attrNames, router.results[0], 'routerId')
+        if (!name)
+          name = QDRService.valFor(attrNames, router.results[0], 'name')
+        names.push(name);
+      }
+
+      while (names.indexOf(newName) >= 0) {
+        newName = "R." + nameIndex++;
+      }
+      return newName;
+    }
+
+    $scope.$watch(function () {return $scope.addingNode.trigger}, function (newValue, oldValue) {
+      if (newValue == 'editNode') {
+        $scope.addingNode.trigger = "";
+        editNode();
+      }
+    })
+
+    function editNode() {
+      doAddDialog(NewRouterName);
+    };
+    $scope.reverseLink = function () {
+      if (!mousedown_link)
+        return;
+      var d = mousedown_link;
+      var tmp = d.left;
+      d.left = d.right;;
+      d.right = tmp;
+        restart(false);
+        tick();
+    }
+    $scope.removeLink = function () {
+      if (!mousedown_link)
+        return;
+      var d = mousedown_link;
+       links.every( function (l, i) {
+        if (l.source.id == d.source.id && l.target.id == d.target.id) {
+              links.splice(i, 1);
+          force.links(links).start();
+          return false; // exit the 'every' loop
+        }
+        return true;
+      });
+        restart(false);
+        tick();
+    }
+    $scope.setFixed = function (b) {
+      if ($scope.contextNode) {
+        $scope.contextNode.fixed = b;
+      }
+      restart();
+    }
+    $scope.isFixed = function () {
+      if (!$scope.contextNode)
+        return false;
+      return ($scope.contextNode.fixed & 0b1);
+    }
+
+    // event handlers for popup context menu
+    $(document).mousemove(function (e) {
+        mouseX = e.clientX;
+        mouseY = e.clientY;
+        //console.log("("+mouseX+"," + mouseY+")")
+    });
+    $(document).mousemove();
+    $(document).click(function (e) {
+      $scope.contextNode = null;
+      $(".contextMenu").fadeOut(200);
+    });
+
+    // set up SVG for D3
+    var colors = {'inter-router': "#EAEAEA", 'normal': "#F0F000", 'on-demand': '#00F000'};
+    var radii = {'inter-router': 25, 'normal': 15, 'on-demand': 15};
+    var radius = 25;
+    var radiusNormal = 15;
+    var svg, lsvg;
+    var force;
+    var animate = false; // should the force graph organize itself when it is displayed
+    var path, circle;
+    var savedKeys = {};
+    var width = 0;
+    var height = 0;
+
+    var getSizes = function () {
+      var legendWidth = 196;
+      var gap = 5;
+      var width = $('.qdrTopology').width() - gap - legendWidth;
+      var top = $('#topology').offset().top
+      var tpformHeight = $('#topologyForm').height()
+      var height = window.innerHeight - tpformHeight - top - gap;
+      if (height < 400)
+        height = 400;
+/*
+      QDR.log.debug("window.innerHeight:" + window.innerHeight +
+        " tpformHeight:" + tpformHeight +
+        " top:" + top +
+        " gap:" + gap +
+        " width:" + width +
+        " height:" + height)
+*/
+      if (width < 10 || height < 30) {
+        QDR.log.info("page width and height are abnormal w:" + width + " height:" + height)
+        return [0,0];
+      }
+      return [width, height]
+    }
+    var resize = function () {
+      var 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();
+      }
+    }
+    window.addEventListener('resize', resize);
+    var sizes = getSizes()
+    width = sizes[0]
+    height = sizes[1]
+    height = 300
+    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'.
+    var nodes = [];
+    var links = [];
+
+    var aNode = function (id, name, nodeType, nodeInfo, nodeIndex, x, y, resultIndex, fixed, properties) {
+      properties = properties || {};
+      var routerId;
+      if (nodeInfo) {
+        var node = nodeInfo[id];
+        if (node) {
+          var router = node['.router'];
+          routerId = QDRService.valFor(router.attributeNames, router.results[0], 'id')
+          if (!routerId)
+            routerId = QDRService.valFor(router.attributeNames, router.results[0], 'routerId')
+        }
+      }
+      return {   key: id,
+        name: name,
+        nodeType: nodeType,
+        properties: properties,
+        routerId: routerId,
+        x: x,
+        y: y,
+        id: nodeIndex,
+        resultIndex: resultIndex,
+        fixed: fixed,
+        cls: name == NewRouterName ? 'temp' : ''
+      };
+    };
+
+
+        var initForm = function (attributes, results, entityType, formFields) {
+
+            while(formFields.length > 0) {
+                // remove all existing attributes
+                    formFields.pop();
+            }
+
+            for (var i=0; i<attributes.length; ++i) {
+                var name = attributes[i];
+                var val = results[i];
+                var desc = "";
+                if (entityType.attributes[name])
+                    if (entityType.attributes[name].description)
+                        desc = entityType.attributes[name].description;
+
+                formFields.push({'attributeName': name, 'attributeValue': val, 'description': desc});
+            }
+        }
+
+    // initialize the nodes and links array from the QDRService.topology._nodeInfo object
+    var initForceGraph = function () {
+      nodes = [];
+      links = [];
+
+      svg = d3.select('#topology')
+        .append('svg')
+        .attr("id", "SVG_ID")
+        .attr('width', width)
+        .attr('height', height)
+        .on("contextmenu", function(d) {
+          if (QDR.isHorizon)
+            return;
+          if (d3.event.defaultPrevented)
+            return;
+          d3.event.preventDefault();
+          if ($scope.addingNode.step != 0)
+            return;
+          if (d3.select('#svg_context_menu').style('display') !== 'block')
+            $(document).click();
+          d3.select('#svg_context_menu')
+            .style('left', (mouseX + $(document).scrollLeft()) + "px")
+            .style('top', (mouseY + $(document).scrollTop()) + "px")
+            .style('display', 'block');
+        })
+        .on('click', function (d) {
+          removeCrosssection()
+        });
+
+      $(document).keyup(function(e) {
+        if (e.keyCode === 27) {
+          removeCrosssection()
+        }
+      });
+
+      // the legend
+      lsvg = d3.select("#svg_legend")
+         .append('svg')
+        .attr('id', 'svglegend')
+      lsvg = lsvg.append('svg:g')
+        .attr('transform', 'translate('+(radii['inter-router']+2)+','+(radii['inter-router']+2)+')')
+        .selectAll('g');
+
+      // mouse event vars
+      selected_node = null;
+      selected_link = null;
+      mousedown_link = null;
+      mousedown_node = null;
+      mouseup_node = null;
+
+      // initialize the list of nodes
+      var yInit = 10;
+      var nodeInfo = QDRService.topology.nodeInfo();
+      var nodeCount = Object.keys(nodeInfo).length;
+      for (var id in nodeInfo) {
+        var name = QDRService.nameFromId(id);
+                // if we have any new nodes, animate the force graph to position them
+        var position = angular.fromJson(localStorage[name]);
+        if (!angular.isDefined(position)) {
+            animate = true;
+            position = {x: width / 4 + ((width / 2)/nodeCount) * nodes.length,
+                        y: 200 + yInit,
+                        fixed: false};
+        }
+        if (position.y > height)
+          position.y = 200 - yInit;
+        nodes.push( aNode(id, name, "inter-router", nodeInfo, nodes.length, position.x, position.y, undefined, position.fixed) );
+        yInit *= -1;
+        //QDR.log.debug("adding node " + nodes.length-1);
+      }
+
+      // initialize the list of links
+      var source = 0;
+      var client = 1;
+      for (var id in nodeInfo) {
+        var onode = nodeInfo[id];
+        var conns = onode['.connection'].results;
+        var attrs = onode['.connection'].attributeNames;
+        var parent = getNodeIndex(QDRService.nameFromId(id));
+        //QDR.log.debug("external client parent is " + parent);
+        var normalsParent = {console: undefined, client: undefined}; // 1st normal node for this parent
+
+        for (var j = 0; j < conns.length; j++) {
+                    var role = QDRService.valFor(attrs, conns[j], "role");
+                    var properties = QDRService.valFor(attrs, conns[j], "properties") || {};
+                    var dir = QDRService.valFor(attrs, conns[j], "dir");
+          if (role == "inter-router") {
+            var connId = QDRService.valFor(attrs, conns[j], "container");
+            var target = getContainerIndex(connId);
+            if (target >= 0)
+              getLink(source, target, dir);
+          } else if (role == "normal" || role == "on-demand") {
+            // not a router, but an external client
+            //QDR.log.debug("found an external client for " + id);
+            var name = QDRService.nameFromId(id) + "." + client;
+            //QDR.log.debug("external client name is  " + name + " and the role is " + role);
+
+                        // if we have any new clients, animate the force graph to position them
+                        var position = angular.fromJson(localStorage[name]);
+                        if (!angular.isDefined(position)) {
+                            animate = true;
+                            position = {x: nodes[parent].x + 40 + Math.sin(Math.PI/2 * client),
+                                        y: nodes[parent].y + 40 + Math.cos(Math.PI/2 * client),
+                                        fixed: false};
+                        }
+            if (position.y > height)
+              position.y = nodes[parent].y + 40 + Math.cos(Math.PI/2 * client)
+            var node = aNode(id, name, role, nodeInfo, nodes.length, position.x, position.y, j, position.fixed, properties)
+            var nodeType = QDRService.isAConsole(properties, QDRService.valFor(attrs, conns[j], "identity"), role, node.key)
+
+            if (role === 'normal') {
+              node.user = QDRService.valFor(attrs, conns[j], "user")
+              node.isEncrypted = QDRService.valFor(attrs, conns[j], "isEncrypted")
+              node.host = QDRService.valFor(attrs, conns[j], "host")
+              node.connectionId = QDRService.valFor(attrs, conns[j], "identity")
+
+              if (!normalsParent[nodeType]) {
+                normalsParent[nodeType] = node;
+                nodes.push(  node );
+                node.normals = [node];
+                // now add a link
+                getLink(parent, nodes.length-1, dir);
+                client++;
+              } else {
+                normalsParent[nodeType].normals.push(node)
+              }
+            } else {
+              nodes.push( node)
+              // now add a link
+              getLink(parent, nodes.length-1, dir);
+              client++;
+            }
+          }
+        }
+        source++;
+      }
+
+            $scope.schema = QDRService.schema;
+      // init D3 force layout
+      force = d3.layout.force()
+        .nodes(nodes)
+        .links(links)
+        .size([width, height])
+        .linkDistance(function(d) { return d.target.nodeType === 'inter-router' ? 150 : 65 })
+        .charge(-1800)
+        .friction(.10)
+        .gravity(0.0001)
+        .on('tick', tick)
+        .start()
+
+      svg.append("svg:defs").selectAll('marker')
+        .data(["end-arrow", "end-arrow-selected"])      // Different link/path types can be defined here
+        .enter().append("svg:marker")    // This section adds in the arrows
+        .attr("id", String)
+        .attr("viewBox", "0 -5 10 10")
+        //.attr("refX", 25)
+        .attr("markerWidth", 4)
+        .attr("markerHeight", 4)
+        .attr("orient", "auto")
+        .append("svg:path")
+        .attr('d', 'M 0 -5 L 10 0 L 0 5 z')
+
+      svg.append("svg:defs").selectAll('marker')
+        .data(["start-arrow", "start-arrow-selected"])      // Different link/path types can be defined here
+        .enter().append("svg:marker")    // This section adds in the arrows
+        .attr("id", String)
+        .attr("viewBox", "0 -5 10 10")
+        .attr("refX", 5)
+        .attr("markerWidth", 4)
+        .attr("markerHeight", 4)
+        .attr("orient", "auto")
+        .append("svg:path")
+        .attr('d', 'M 10 -5 L 0 0 L 10 5 z');
+
+      // handles to link and node element groups
+      path = svg.append('svg:g').selectAll('path'),
+      circle = svg.append('svg:g').selectAll('g');
+
+      force.on('end', function() {
+        //QDR.log.debug("force end called");
+        circle
+          .attr('cx', function(d) {
+            localStorage[d.name] = angular.toJson({x: d.x, y: d.y, fixed: d.fixed});
+            return d.x; });
+      });
+
+      // app starts here
+      restart(false);
+          force.start();
+      setTimeout(function () {
+            updateForm(Object.keys(QDRService.topology.nodeInfo())[0], 'router', 0);
+      }, 10)
+
+    }
+
+    function updateForm (key, entity, resultIndex) {
+      var nodeInfo = QDRService.topology.nodeInfo();
+      var onode = nodeInfo[key]
+      if (onode) {
+        var nodeResults = onode['.' + entity].results[resultIndex]
+        var nodeAttributes = onode['.' + entity].attributeNames
+        var attributes = nodeResults.map( function (row, i) {
+          return {
+            attributeName: nodeAttributes[i],
+            attributeValue: row
+          }
+        })
+        // sort by attributeName
+        attributes.sort( function (a, b) { return a.attributeName.localeCompare(b.attributeName) })
+
+        // move the Name first
+        var nameIndex = attributes.findIndex ( function (attr) {
+          return attr.attributeName === 'name'
+        })
+        if (nameIndex >= 0)
+          attributes.splice(0, 0, attributes.splice(nameIndex, 1)[0]);
+        // get the list of ports this router is listening on
+        if (entity === 'router') {
+          var listeners = onode['.listener'].results;
+          var listenerAttributes = onode['.listener'].attributeNames;
+          var normals = listeners.filter ( function (listener) {
+            return QDRService.valFor( listenerAttributes, listener, 'role') === 'normal';
+          })
+          var ports = []
+          normals.forEach (function (normalListener) {
+            ports.push(QDRService.valFor( listenerAttributes, normalListener, 'port'))
+          })
+          // add as 2nd row
+          if (ports.length)
+            attributes.splice(1, 0, {attributeName: 'Listening on', attributeValue: ports, description: 'The port on which this router is listening for connections'});
+        }
+
+        $scope.$broadcast('showEntityForm', {entity: entity, attributes: attributes})
+      }
+      if (!$scope.$$phase) $scope.$apply()
+    }
+
+        function getContainerIndex(_id) {
+            var nodeIndex = 0;
+            var nodeInfo = QDRService.topology.nodeInfo();
+            for (var id in nodeInfo) {
+                var node = nodeInfo[id]['.router'];
+                // there should be only one router entity for each node, so using results[0] should be fine
+                if (QDRService.valFor( node.attributeNames, node.results[0], "id") === _id)
+                    return nodeIndex;
+                if (QDRService.valFor( node.attributeNames, node.results[0], "routerId") === _id)
+                    return nodeIndex;
+                nodeIndex++
+            }
+      // there was no router.id that matched, check deprecated router.routerId
+            nodeIndex = 0;
+            for (var id in nodeInfo) {
+                var node = nodeInfo[id]['.container'];
+        if (node) {
+          if (QDRService.valFor ( node.attributeNames, node.results[0], "containerName") === _id)
+            return nodeIndex;
+        }
+        nodeIndex++
+      }
+            //QDR.log.warn("unable to find containerIndex for " + _id);
+            return -1;
+        }
+
+        function getNodeIndex (_id) {
+            var nodeIndex = 0;
+            var nodeInfo = QDRService.topology.nodeInfo();
+            for (var id in nodeInfo) {
+                if (QDRService.nameFromId(id) == _id) return nodeIndex;
+                nodeIndex++
+            }
+            QDR.log.warn("unable to find nodeIndex for " + _id);
+            return -1;
+        }
+
+        function getLink (_source, _target, dir, cls) {
+            for (var i=0; i < links.length; i++) {
+                var s = links[i].source, t = links[i].target;
+                if (typeof links[i].source == "object") {
+                    s = s.id;
+                    t = t.id;
+        }
+                if (s == _source && t == _target) {
+                    return i;
+                }
+        // same link, just reversed
+                if (s == _target && t == _source) {
+                    return -i;
+        }
+            }
+
+            //QDR.log.debug("creating new link (" + (links.length) + ") between " + nodes[_source].name + " and " + nodes[_target].name);
+            var link = {
+                source: _source,
+                target: _target,
+                left: dir != "out",
+                right: dir == "out",
+                cls: cls
+            };
+            return links.push(link) - 1;
+        }
+
+
+      function resetMouseVars() {
+          mousedown_node = null;
+          mouseup_node = null;
+          mousedown_link = null;
+      }
+
+      // update force layout (called automatically each iteration)
+      function tick() {
+          circle.attr('transform', function (d) {
+                var 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) {
+        //QDR.log.debug("in tick for d");
+        //console.dump(d);
+                var 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;
+                }
+        var 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));
+
+              var deltaX = dtx - dsx,
+                  deltaY = dty - dsy,
+                  dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
+                  normX = deltaX / dist,
+                  normY = deltaY / dist;
+                  var sourceX = dsx + (sourcePadding * normX),
+                  sourceY = dsy + (sourcePadding * normY),
+                  targetX = dtx - (targetPadding * normX),
+                  targetY = dty - (targetPadding * normY);
+          sourceX = Math.max(0, Math.min(width, sourceX))
+          sourceY = Math.max(0, Math.min(width, sourceY))
+          targetX = Math.max(0, Math.min(width, targetX))
+          targetY = Math.max(0, Math.min(width, targetY))
+
+              return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
+          });
+
+          if (!animate) {
+              animate = true;
+              force.stop();
+          }
+      }
+
+        // highlight the paths between the selected node and the hovered node
+        function findNextHopNode(from, d) {
+            // d is the node that the mouse is over
+            // from is the selected_node ....
+            if (!from)
+                return null;
+
+            if (from == d)
+                return selected_node;
+
+            //QDR.log.debug("finding nextHop from: " + from.name + " to " + d.name);
+            var sInfo = QDRService.topology.nodeInfo()[from.key];
+
+            if (!sInfo) {
+                QDR.log.warn("unable to find topology node info for " + from.key);
+                return null;
+            }
+
+            // find the hovered name in the selected name's .router.node results
+            if (!sInfo['.router.node'])
+                return null;
+            var aAr = sInfo['.router.node'].attributeNames;
+            var vAr = sInfo['.router.node'].results;
+            for (var hIdx=0; hIdx<vAr.length; ++hIdx) {
+                var addrT = QDRService.valFor(aAr, vAr[hIdx], "id" );
+                if (addrT == d.name) {
+                    //QDR.log.debug("found " + d.name + " at " + hIdx);
+                    var nextHop = QDRService.valFor(aAr, vAr[hIdx], "nextHop");
+                    //QDR.log.debug("nextHop was " + nextHop);
+                    return (nextHop == null) ? nodeFor(addrT) : nodeFor(nextHop);
+                }
+            }
+            return null;
+        }
+
+        function nodeFor(name) {
+            for (var i=0; i<nodes.length; ++i) {
+                if (nodes[i].name == name)
+                    return nodes[i];
+            }
+            return null;
+        }
+
+        function linkFor(source, target) {
+            for (var i=0; i<links.length; ++i) {
+                if ((links[i].source == source) && (links[i].target == target))
+                    return links[i];
+                if ((links[i].source == target) && (links[i].target == source))
+                    return links[i];
+            }
+            // the selected node was a client/broker
+            //QDR.log.debug("failed to find a link between ");
+            //console.dump(source);
+            //QDR.log.debug(" and ");
+            //console.dump(target);
+            return null;
+        }
+
+    function clearPopups() {
+      d3.select("#crosssection").style("display", "none");
+      $('.hastip').empty();
+      d3.select("#multiple_details").style("visibility", "hidden")
+      d3.select("#link_details").style("visibility", "hidden")
+      d3.select('#node_context_menu').style('display', 'none');
+    }
+    function removeCrosssection() {
+      setTimeout(function () {
+        d3.select("[id^=tooltipsy]").remove()
+        $('.hastip').empty();
+      }, 1010);
+      d3.select("#crosssection svg g").transition()
+        .duration(1000)
+        .attr("transform", "scale(0)")
+          .style("opacity", 0)
+          .each("end", function (d) {
+              d3.select("#crosssection svg").remove();
+              d3.select("#crosssection").style("display","none");
+          });
+      d3.select("#multiple_details").transition()
+        .duration(500)
+        .style("opacity", 0)
+        .each("end", function (d) {
+            d3.select("#multiple_details").style("visibility", "hidden")
+            stopUpdateConnectionsGrid();
+        })
+      hideLinkDetails();
+    }
+
+    // takes the nodes and links array of objects and adds svg elements for everything that hasn't already
+    // been added
+    function restart(start) {
+      circle.call(force.drag);
+
+      // path (link) group
+      path = path.data(links);
+
+      // update existing links
+      path.classed('selected', function(d) { return d === selected_link; })
+        .classed('highlighted', function(d) { return d.highlighted; } )
+        .classed('temp', function(d) { return d.cls == 'temp'; } )
+          .attr('marker-start', function(d) {
+            var sel = d===selected_link ? '-selected' : '';
+            return d.left ? 'url('+urlPrefix+'#start-arrow' + sel + ')' : ''; })
+          .attr('marker-end', function(d) {
+            var sel = d===selected_link ? '-selected' : '';
+            return d.right ? 'url('+urlPrefix+'#end-arrow' + sel +')' : ''; })
+
+
+      // add new links. if links[] is longer than the existing paths, add a new path for each new element
+      path.enter().append('svg:path')
+        .attr('class', 'link')
+                .attr('marker-start', function(d) {
+                        var sel = d===selected_link ? '-selected' : '';
+            return d.left ? 'url('+urlPrefix+'#start-arrow' + sel + ')' : ''; })
+                .attr('marker-end', function(d) {
+          var sel = d===selected_link ? '-selected' : '';
+                    return d.right ? 'url('+urlPrefix+'#end-arrow' + sel + ')' : ''; })
+            .classed('temp', function(d) { return d.cls == 'temp'; } )
+        // mouseover a line
+        .on('mouseover', function (d) {
+          if($scope.addingNode.step > 0) {
+            if (d.cls == 'temp') {
+                d3.select(this).classed('over', true);
+            }
+            return;
+          }
+              //QDR.log.debug("showing connections form");
+          var resultIndex = 0; // the connection to use
+                    var left = d.left ? d.target : d.source;
+          // right is the node that the arrow points to, left is the other node
+          var right = d.left ? d.source : d.target;
+          var onode = QDRService.topology.nodeInfo()[left.key];
+          // loop through all the connections for left, and find the one for right
+          if (!onode || !onode['.connection'])
+            return;
+                    // update the info dialog for the link the mouse is over
+                    if (!selected_node && !selected_link) {
+                        for (resultIndex=0; resultIndex < onode['.connection'].results.length; ++resultIndex) {
+                            var conn = onode['.connection'].results[resultIndex];
+                            /// find the connection whose container is the right's name
+                            var name = QDRService.valFor(onode['.connection'].attributeNames, conn, "container");
+                            if (name == right.routerId) {
+                                break;
+                            }
+                        }
+                        // did not find connection. this is a connection to a non-interrouter node
+                        if (resultIndex === onode['.connection'].results.length) {
+                            // use the non-interrouter node's connection info
+                            left = d.target;
+                            resultIndex = left.resultIndex;
+                        }
+            if (resultIndex)
+                            updateForm(left.key, 'connection', resultIndex);
+                    }
+
+          mousedown_link = d;
+          selected_link = mousedown_link;
+          restart();
+        })
+        // mouseout a line
+        .on('mouseout', function (d) {
+          if($scope.addingNode.step > 0) {
+            if (d.cls == 'temp') {
+                d3.select(this).classed('over', false);
+            }
+            return;
+          }
+              //QDR.log.debug("showing connections form");
+          selected_link = null;
+          restart();
+        })
+        // contextmenu for a line
+        .on("contextmenu", function(d) {
+          $(document).click();
+          d3.event.preventDefault();
+          if (d.cls !== "temp")
+              return;
+
+          mousedown_link = d;
+          d3.select('#link_context_menu')
+            .style('left', (mouseX + $(document).scrollLeft()) + "px")
+            .style('top', (mouseY + $(document).scrollTop()) + "px")
+            .style('display', 'block');
+        })
+        // clicked on a line
+        .on("click", function (d) {
+          var clickPos = d3.mouse(this);
+          d3.event.stopPropagation();
+          clearPopups();
+          var diameter = 400;
+          var format = d3.format(",d");
+          var pack = d3.layout.pack()
+              .size([diameter - 4, diameter - 4])
+              .padding(-10)
+              .value(function(d) { return d.size; });
+
+          d3.select("#crosssection svg").remove();
+          var svg = d3.select("#crosssection").append("svg")
+              .attr("width", diameter)
+              .attr("height", diameter)
+          var svgg = svg.append("g")
+              .attr("transform", "translate(2,2)");
+
+          var root = {
+            name: " Links between " + d.source.name + " and " + d.target.name,
+            children: []
+          }
+          var nodeInfo = QDRService.topology.nodeInfo();
+          var connections = nodeInfo[d.source.key]['.connection'];
+          var containerIndex = connections.attributeNames.indexOf('container');
+          connections.results.some ( function (connection) {
+            if (connection[containerIndex] == d.target.routerId) {
+              root.attributeNames = connections.attributeNames;
+              root.obj = connection;
+              root.desc = "Connection";
+              return true;    // stop looping after 1 match
+            }
+            return false;
+          })
+
+          // find router.links where link.remoteContainer is d.source.name
+          var links = nodeInfo[d.source.key]['.router.link'];
+          var identityIndex = connections.attributeNames.indexOf('identity')
+          var roleIndex = connections.attributeNames.indexOf('role')
+          var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+          var linkTypeIndex = links.attributeNames.indexOf('linkType');
+          var nameIndex = links.attributeNames.indexOf('name');
+          var linkDirIndex = links.attributeNames.indexOf('linkDir');
+
+          if (roleIndex < 0 || identityIndex < 0 || connectionIdIndex < 0
+            || linkTypeIndex < 0 || nameIndex < 0 || linkDirIndex < 0)
+            return;
+          links.results.forEach ( function (link) {
+            if (root.obj && link[connectionIdIndex] == root.obj[identityIndex] && link[linkTypeIndex] == root.obj[roleIndex])
+              root.children.push (
+                { name: " " + link[linkDirIndex] + " ",
+                size: 100,
+                obj: link,
+                desc: "Link",
+                attributeNames: links.attributeNames
+              })
+          })
+          if (root.children.length == 0)
+            return;
+          var node = svgg.datum(root).selectAll(".node")
+            .data(pack.nodes)
+            .enter().append("g")
+            .attr("class", function(d) { return d.children ? "parent node hastip" : "leaf node hastip"; })
+            .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" + (!d.children ? "scale(0.9)" : ""); })
+            .attr("title", function (d) {
+              var title = "<h4>" + d.desc + "</h4><table class='tiptable'><tbody>";
+              if (d.attributeNames)
+                d.attributeNames.forEach( function (n, i) {
+                  title += "<tr><td>" + n + "</td><td>";
+                  title += d.obj[i] != null ? d.obj[i] : '';
+                  title += '</td></tr>';
+                })
+              title += "</tbody></table>"
+              return title
+            })
+            node.append("circle")
+              .attr("r", function(d) { return d.r; });
+
+//          node.filter(function(d) { return !d.children; }).append("text")
+            node.append("text")
+              .attr("dy", function (d) { return d.children ? "-10em" : ".5em"})
+              .style("text-anchor", "middle")
+              .text(function(d) {
+                  return d.name.substring(0, d.r / 3);
+              });
+          $('.hastip').tooltipsy({ alignTo: 'cursor'});
+          svgg.attr("transform", "translate(2,2) scale(0.01)")
+
+          var 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")
+            })
+        })
+
+          // remove old links
+          path.exit().remove();
+
+
+          // circle (node) group
+          // nodes are known by id
+          circle = circle.data(nodes, function (d) {
+              return d.id;
+          });
+
+          // update existing nodes visual states
+          circle.selectAll('circle')
+              .classed('selected', function (d) { return (d === selected_node) })
+              .classed('fixed', function (d) { return (d.fixed & 0b1) })
+
+      // add new circle nodes. if nodes[] is longer than the existing paths, add a new path for each new element
+          var g = circle.enter().append('svg:g')
+            .classed('multiple', function(d) { return (d.normals && d.normals.length > 1)  } )
+
+      var appendCircle = function (g) {
+        // add new circles and set their attr/class/behavior
+            return g.append('svg:circle')
+                .attr('class', 'node')
+                .attr('r', function (d) { return radii[d.nodeType] } )
+                .classed('fixed', function (d) {return d.fixed})
+                  .classed('temp', function(d) { return QDRService.nameFromId(d.key) == '__internal__'; } )
+                  .classed('normal', function(d) { return d.nodeType == 'normal' } )
+                  .classed('inter-router', function(d) { return d.nodeType == 'inter-router' } )
+                  .classed('on-demand', function(d) { return d.nodeType == 'on-demand' } )
+                  .classed('console', function(d) { return QDRService.isConsole(d) } )
+                  .classed('artemis', function(d) { return QDRService.isArtemis(d) } )
+                  .classed('qpid-cpp', function(d) { return QDRService.isQpid(d) } )
+                  .classed('client', function(d) { return d.nodeType === 'normal' && !d.properties.console_identifier } )
+      }
+      appendCircle(g)
+        .on('mouseover', function (d) { // mouseover a circle
+          if ($scope.addingNode.step > 0) {
+            d3.select(this).attr('transform', 'scale(1.1)');
+            return;
+          }
+          if (!selected_node) {
+            if (d.nodeType === 'inter-router') {
+              //QDR.log.debug("showing general form");
+              updateForm(d.key, 'router', 0);
+            } else if (d.nodeType === 'normal' || d.nodeType === 'on-demand') {
+              //QDR.log.debug("showing connections form");
+              updateForm(d.key, 'connection', d.resultIndex);
+            }
+          }
+
+          if (d === mousedown_node)
+            return;
+          //if (d === selected_node)
+          //    return;
+          // enlarge target node
+          d3.select(this).attr('transform', 'scale(1.1)');
+          // highlight the next-hop route from the selected node to this node
+          mousedown_node = null;
+          if (!selected_node) {
+              return;
+          }
+          setTimeout(nextHop, 1, selected_node, d);
+        })
+        .on('mouseout', function (d) { // mouseout a circle
+          // unenlarge target node
+          d3.select(this).attr('transform', '');
+          for (var i=0; i<links.length; ++i) {
+            links[i]['highlighted'] = false;
+          }
+          restart();
+        })
+        .on('mousedown', function (d) { // mousedown a 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.parentElement.parentElement.parentElement).slice();
+        })
+        .on('mouseup', function (d) {  // mouseup a circle
+          if (!mousedown_node)
+            return;
+
+          selected_link = null;
+          // unenlarge target node
+          d3.select(this).attr('transform', '');
+
+          // check for drag
+          mouseup_node = d;
+          var mySvg = this.parentElement.parentElement.parentElement;
+          // if we dragged the node, make it fixed
+          var cur_mouse = d3.mouse(mySvg);
+          if (cur_mouse[0] != initial_mouse_down_position[0] ||
+            cur_mouse[1] != initial_mouse_down_position[1]) {
+              console.log("mouse pos changed. making this node fixed")
+              d3.select(this).classed("fixed", d.fixed = true);
+              resetMouseVars();
+              return;
+          }
+
+          // we didn't drag, we just clicked on the node
+          if ($scope.addingNode.step > 0) {
+            if (d.nodeType !== 'inter-router')
+              return;
+            if (QDRService.nameFromId(d.key) == '__internal__')
+              return;
+
+            // add a link from the clicked node to the new node
+            getLink(d.id, nodes.length-1, "in", "temp");
+            $scope.addingNode.hasLink = true;
+            if (!$scope.$$phase) $scope.$apply()
+            // add new elements to the svg
+            force.links(links).start();
+            restart();
+            return;
+          }
+
+          // if this node was selected, unselect it
+          if (mousedown_node === selected_node) {
+            selected_node = null;
+          }
+          else {
+            if (d.nodeType !== 'normal' && d.nodeType !== 'on-demand')
+              selected_node = mousedown_node;
+          }
+          for (var i=0; i<links.length; ++i) {
+            links[i]['highlighted'] = false;
+          }
+          mousedown_node = null;
+          if (!$scope.$$phase) $scope.$apply()
+            restart(false);
+        })
+        .on("dblclick", function (d) {  // dblclick a circle
+          if (d.fixed) {
+            d3.select(this).classed("fixed", d.fixed = false);
+            force.start();  // let the nodes move to a new position
+          }
+          if (QDRService.nameFromId(d.key) == '__internal__') {
+            editNode();
+            if (!$scope.$$phase) $scope.$apply()
+          }
+        })
+        .on("contextmenu", function(d) { // rightclick a circle
+          $(document).click();
+          d3.event.preventDefault();
+          $scope.contextNode = d;
+          if (!$scope.$$phase) $scope.$apply()     // we just changed a scope valiable during an async event
+          var bounds = $(QDR.offsetParent).offset()
+          d3.select('#node_context_menu')
+            .style('left', (mouseX - bounds.left + $(document).scrollLeft()) + "px")
+            .style('top', (mouseY - bounds.top + $(document).scrollTop()) + "px")
+            .style('display', 'block');
+        })
+        .on("click", function (d) {  // leftclick a circle
+          var clickPos = d3.mouse(this);
+          clearPopups();
+          if (!d.normals) {
+            // circle was a router or a broker
+            if ( QDRService.isArtemis(d) && Core.ConnectionName === 'Artemis' ) {
+              $location.path('/jmx/attributes?tab=artemis&con=Artemis')
+            }
+            return;
+          }
+          // circle was a client or console
+          d3.event.stopPropagation();
+          startUpdateConnectionsGrid(d, clickPos);
+        })
+
+      var appendContent = function (g) {
+        // show node IDs
+        g.append('svg:text')
+          .attr('x', 0)
+          .attr('y', function (d) {
+            var y = 6;
+            if (QDRService.isArtemis(d))
+              y = 8;
+            else if (QDRService.isQpid(d))
+              y = 9;
+            else if (d.nodeType === 'inter-router')
+              y = 4;
+            return y;})
+          .attr('class', 'id')
+          .classed('console', function(d) { return QDRService.isConsole(d) } )
+          .classed('normal', function(d) { return d.nodeType === 'normal' } )
+          .classed('on-demand', function(d) { return d.nodeType === 'on-demand' } )
+          .classed('artemis', function(d) { return QDRService.isArtemis(d) } )
+          .classed('qpid-cpp', function(d) { return QDRService.isQpid(d) } )
+          .text(function (d) {
+            if (QDRService.isConsole(d)) {
+              return '\uf108'; // icon-desktop for this console
+            }
+            if (QDRService.isArtemis(d)) {
+              return '\ue900'
+            }
+            if (QDRService.isQpid(d)) {
+              return '\ue901';
+            }
+            if (d.nodeType === 'normal')
+              return '\uf109'; // icon-laptop for clients
+                    return d.name.length>7 ? d.name.substr(0,6)+'...' : d.name;
+          });
+      }
+      appendContent(g)
+
+      var appendTitle = function (g) {
+        g.append("svg:title").text(function (d) {
+          var x = '';
+          if (d.normals && d.normals.length > 1)
+            x = " x " + d.normals.length;
+          if (QDRService.isConsole(d)) {
+            return 'Dispatch console' + x
+          }
+          if (d.properties.product == 'qpid-cpp') {
+            return 'Broker - qpid-cpp' + x
+          }
+          if ( QDRService.isArtemis(d) ) {
+            return 'Broker - Artemis' + x
+          }
+          return d.nodeType == 'normal' ? 'client' + x : (d.nodeType == 'on-demand' ? 'broker' : 'Router ' + d.name)
+        })
+      }
+      appendTitle(g);
+
+      // remove old nodes
+      circle.exit().remove();
+
+      // add subcircles
+      svg.selectAll('.subcircle').remove();
+      var multiples = svg.selectAll('.multiple')
+      multiples.each( function (d) {
+        d.normals.forEach( function (n, i) {
+          if (i<d.normals.length-1 && i<3) // only show a few shadow circles
+            this.insert('svg:circle', ":first-child")
+            .attr('class', 'subcircle node')
+            .attr('r', 15 - i)
+            .attr('transform', "translate("+ 4 * (i+1) +", 0)")
+        }, d3.select(this))
+      })
+
+      // dynamically create the legend based on which node types are present
+      var legendNodes = [];
+      legendNodes.push(aNode("Router", "", "inter-router", undefined, 0, 0, 0, 0, false, {}))
+
+      if (!svg.selectAll('circle.console').empty()) {
+        legendNodes.push(aNode("Dispatch console", "", "normal", undefined, 1, 0, 0, 0, false, {console_identifier: 'Dispatch console'}))
+      }
+      if (!svg.selectAll('circle.client').empty()) {
+        legendNodes.push(aNode("Client", "", "normal", undefined, 2, 0, 0, 0, false, {}))
+      }
+      if (!svg.selectAll('circle.qpid-cpp').empty()) {
+        legendNodes.push(aNode("Qpid cpp broker", "", "on-demand", undefined, 3, 0, 0, 0, false, {product: 'qpid-cpp'}))
+      }
+      if (!svg.selectAll('circle.artemis').empty()) {
+        legendNodes.push(aNode("Artemis broker", "", "on-demand", undefined, 4, 0, 0, 0, false, {}))
+      }
+      lsvg = lsvg.data(legendNodes, function (d) {
+        return d.id;
+      });
+      var lg = lsvg.enter().append('svg:g')
+        .attr('transform', function (d, i) {
+          // 45px between lines and add 10px space after 1st line
+          return "translate(0, "+(45*i+(i>0?10:0))+")"
+        })
+
+      appendCircle(lg)
+      appendContent(lg)
+      appendTitle(lg)
+      lg.append('svg:text')
+        .attr('x', 35)
+        .attr('y', 6)
+        .attr('class', "label")
+        .text(function (d) {return d.key })
+      lsvg.exit().remove();
+      var svgEl = document.getElementById('svglegend')
+      if (svgEl) {
+        var 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();
+    }
+
+    // show the links popup and update it periodically
+    var startUpdateConnectionsGrid = function (d, clickPos) {
+      // called every update tick
+      var extendConnections = function () {
+        $scope.multiData = []
+        var normals = d.normals;
+        // find updated normals for d
+        d3.selectAll('.normal')
+          .each(function(newd) {
+            if (newd.id == d.id && newd.name == d.name) {
+              normals = newd.normals;
+            }
+          });
+        if (normals) {
+          normals.forEach( function (n) {
+            var nodeInfo = QDRService.topology.nodeInfo();
+            var links = nodeInfo[n.key]['.router.link'];
+            var linkTypeIndex = links.attributeNames.indexOf('linkType');
+            var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+            n.linkData = [];
+            links.results.forEach( function (linkArray) {
+              var link = QDRService.flatten(links.attributeNames, linkArray)
+              if (link.linkType === 'endpoint' && link.connectionId === n.connectionId) {
+                var l = {};
+                l.owningAddr = link.owningAddr;
+                l.dir = link.linkDir;
+                if (l.owningAddr && l.owningAddr.length > 2)
+                  if (l.owningAddr[0] === 'M')
+                    l.owningAddr = l.owningAddr.substr(2)
+                  else
+                    l.owningAddr = l.owningAddr.substr(1)
+
+                l.deliveryCount = QDRService.pretty(link.deliveryCount);
+                l.uncounts = QDRService.pretty(link.undeliveredCount + link.unsettledCount)
+                l.adminStatus = link.adminStatus;
+                l.operStatus = link.operStatus;
+                l.identity = link.identity
+                l.connectionId = link.connectionId
+                l.nodeId = n.key
+                l.type = link.type
+                l.name = link.name
+
+                //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;
+          })
+        }
+        $scope.$apply();
+
+        d3.select('#multiple_details')
+          .style({
+            height: ((normals.length + 1) * 30) + 40 + "px",
+            'overflow-y': normals.length > 10 ? 'scroll' : 'hidden'
+          })
+
+      }
+
+      // call extendConnections whenever the background data is updated
+      QDRService.addUpdatedAction("normalsStats", extendConnections)
+      extendConnections();
+      clearPopups();
+      var visibility = 'visible'
+      var left = mouseX + $(document).scrollLeft()
+      var bounds = $("#topology").position()
+      if (d.normals.length === 1) {
+        visibility = 'hidden'
+        left = left - 30;
+        mouseY = mouseY - 20
+      }
+      d3.select('#multiple_details')
+        .style({
+          visibility: visibility,
+          opacity: 1,
+          left: (clickPos[0] + bounds.left) + "px",
+          top:  (clickPos[1] + bounds.top) + "px"})
+      if (d.normals.length === 1) {
+        // simulate a click on the connection to popup the link details
+        $scope.multiDetails.showLinksList( {entity: d} )
+      }
+    }
+
+    var stopUpdateConnectionsGrid = function () {
+      QDRService.delUpdatedAction("normalsStats");
+    }
+
+    function nextHop(thisNode, d) {
+      if ((thisNode) && (thisNode != d)) {
+        var target = findNextHopNode(thisNode, d);
+        //QDR.log.debug("highlight link from node ");
+         //console.dump(nodeFor(selected_node.name));
+         //console.dump(target);
+        if (target) {
+          var hlLink = linkFor(nodeFor(thisNode.name), target);
+          //QDR.log.debug("need to highlight");
+          //console.dump(hlLink);
+          if (hlLink)
+            hlLink['highlighted'] = true;
+          else
+            target = null;
+        }
+        setTimeout(nextHop, 1, target, d);
+      }
+      restart();
+    }
+
+
+    function mousedown() {
+      // prevent I-bar on drag
+      //d3.event.preventDefault();
+
+      // because :active only works in WebKit?
+      svg.classed('active', true);
+    }
+
+    QDRService.addUpdatedAction("topology", function() {
+      //QDR.log.debug("Topology controller was notified that the model was updated");
+      if (hasChanged()) {
+        QDR.log.info("svg graph changed")
+        saveChanged();
+        // TODO: update graph nodes instead of rebuilding entire graph
+        d3.select("#SVG_ID").remove();
+        d3.select("#svg_legend svg").remove();
+        animate = true;
+        initForceGraph();
+        //if ($location.path().startsWith("/topology"))
+        //    Core.notification('info', "Qpid dispatch router topology changed");
+
+      } else {
+        //QDR.log.debug("no changes")
+      }
+    });
+
+    function hasChanged () {
+      // Don't update the underlying topology diagram if we are adding a new node.
+      // Once adding is completed, the topology will update automatically if it has changed
+      if ($scope.addingNode.step > 0)
+        return false;
+      var nodeInfo = QDRService.topology.nodeInfo();
+      if (Object.keys(nodeInfo).length != Object.keys(savedKeys).length)
+        return true;
+      for (var key in nodeInfo) {
+                // if this node isn't in the saved node list
+                if (!savedKeys.hasOwnProperty(key))
+                    return true;
+                // if the number of connections for this node chaanged
+                if (nodeInfo[key]['.connection'].results.length != savedKeys[key]) {
+          /*
+          QDR.log.debug("number of connections changed for " + key);
+          QDR.log.debug("QDRService.topology._nodeInfo[key]['.connection'].results.length");
+          console.dump(QDRService.topology._nodeInfo[key]['.connection'].results.length);
+          QDR.log.debug("savedKeys[key]");
+          console.dump(savedKeys[key]);
+          */
+                    return true;
+                }
+      }
+      return false;
+    };
+    function saveChanged () {
+            savedKeys = {};
+            var nodeInfo = QDRService.topology.nodeInfo();
+            // save the number of connections per node
+        for (var key in nodeInfo) {
+            savedKeys[key] = nodeInfo[key]['.connection'].results.length;
+        }
+      //QDR.log.debug("saving current keys");
+      //console.dump(savedKeys);
+    };
+    // we are about to leave the page, save the node positions
+    $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
+      //QDR.log.debug("locationChangeStart");
+      nodes.forEach( function (d) {
+             localStorage[d.name] = angular.toJson({x: d.x, y: d.y, fixed: d.fixed});
+      });
+            $scope.addingNode.step = 0;
+
+    });
+    // When the DOM element is removed from the page,
+    // AngularJS will trigger the $destroy event on
+    // the scope
+    $scope.$on("$destroy", function( event ) {
+      //QDR.log.debug("scope on destroy");
+      QDRService.stopUpdating();
+      QDRService.delUpdatedAction("topology");
+      d3.select("#SVG_ID").remove();
+      window.removeEventListener('resize', resize);
+    });
+
+    initForceGraph();
+    saveChanged();
+    QDRService.startUpdating();
+
+    function doAddDialog(NewRouterName) {
+      var d = $modal.dialog({
+      dialogClass: "modal dlg-large",
+      backdrop: true,
+      keyboard: true,
+      backdropClick: true,
+          controller: 'QDR.NodeDialogController',
+          templateUrl: 'node-config-template.html',
+          resolve: {
+              newname: function () {
+                  return NewRouterName;
+              }
+          }
+      });
+      d.open().then(function (result) {
+      if (result)
+        doDownloadDialog(result);
+      });
+    };
+
+    function doDownloadDialog(result) {
+      d = modal.dialog({
+      backdrop: true,
+      keyboard: true,
+      backdropClick: true,
+      controller: 'QDR.DownloadDialogController',
+          templateUrl: 'download-dialog-template.html',
+          resolve: {
+              results: function () {
+                  return result;
+              }
+          }
+      });
+      d.open().then(function (result) {
+      //QDR.log.debug("download dialog done")
+      })
+      if (!$scope.$$phase) $scope.$apply()
+    };
+  };
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.download-controller.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.download-controller.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.download-controller.js
new file mode 100644
index 0000000..2eb812f
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.download-controller.js
@@ -0,0 +1,150 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+  'use strict';
+
+  angular
+    .module('horizon.dashboard.dispatch.topology')
+    .controller('horizon.dashboard.dispatch.topology.TopologyDownloadController', TopologyDownloadController);
+
+  TopologyDownloadController.$inject = [
+    '$scope',
+    'horizon.dashboard.dispatch.comService'
+  ]
+
+  function TopologyDownloadController($scope, QDRService, dialog, results) {
+		var result = results.entities;
+		var annotations = results.annotations;
+		var annotationKeys = Object.keys(annotations);
+		var annotationSections = {};
+
+		// use the router's name as the file name if present
+		$scope.newRouterName = 'router';
+		result.forEach( function (e) {
+			if (e.actualName == 'router') {
+				e.attributes.forEach( function (a) {
+					if (a.name == 'name') {
+						$scope.newRouterName = a.value;
+					}
+				})
+			}
+		})
+		$scope.newRouterName = $scope.newRouterName + ".conf";
+
+		var template = $templateCache.get('config-file-header.html');
+		$scope.verbose = true;
+		$scope.$watch('verbose', function (newVal) {
+			if (newVal !== undefined) {
+				// recreate output using current verbose setting
+				getOutput();
+			}
+		})
+
+		var getOutput = function () {
+			$scope.output = template + '\n';
+			$scope.parts = [];
+			var commentChar = '#'
+			result.forEach(function (entity) {
+				// don't output a section for annotations, they get flattened into the entities
+				var section = "";
+				if (entity.icon) {
+					section += "##\n## Add to " + entity.link.__data__.source.name + "'s configuration file\n##\n";
+				}
+				section += "##\n## " + QDRService.humanify(entity.actualName) + " - " + entity.description + "\n##\n";
+				section += entity.actualName + " {\n";
+				entity.attributes.forEach(function (attribute) {
+					if (attribute.input == 'select')
+						attribute.value = attribute.selected;
+
+					// treat values with all spaces and empty strings as undefined
+					attribute.value = String(attribute.value).trim();
+					if (attribute.value === 'undefined' || attribute.value === '')
+						attribute.value = undefined;
+
+					if ($scope.verbose) {
+						commentChar = attribute.required || attribute.value != attribute['default'] ? ' ' : '#';
+						if (!attribute.value) {
+							commentChar = '#';
+							attribute.value = '';
+						}
+						section += commentChar + "    "
+							+ attribute.name + ":" + Array(Math.max(20 - attribute.name.length, 1)).join(" ")
+							+ attribute.value
+						    + Array(Math.max(20 - ((attribute.value)+"").length, 1)).join(" ")
+							+ '# ' + attribute.description
+						    + "\n";
+					} else {
+						if (attribute.value) {
+							if (attribute.value != attribute['default'] || attribute.required)
+								section += "    "
+									+ attribute.name + ":" + Array(20 - attribute.name.length).join(" ")
+									+ attribute.value + "\n";
+
+						}
+					}
+				})
+				section += "}\n\n";
+				// if entity.icon is true, this is a connector intended for another router
+				if (entity.icon)
+					$scope.parts.push({output: section,
+								link: entity.link,
+								name: entity.link.__data__.source.name,
+								references: entity.references});
+				else
+					$scope.output += section;
+
+				// if this section is actually an annotation
+				if (annotationKeys.indexOf(entity.actualName) > -1) {
+					annotationSections[entity.actualName] = section;
+				}
+			})
+			// go back and add annotation sections to the parts
+			$scope.parts.forEach (function (part) {
+				for (var section in annotationSections) {
+					if (part.references.indexOf(section) > -1) {
+						part.output += annotationSections[section];
+					}
+				}
+			})
+			QDR.log.debug($scope.output);
+		}
+
+        // handle the download button click
+        $scope.download = function () {
+			var output = $scope.output + "\n\n"
+			var blob = new Blob([output], { type: 'text/plain;charset=utf-16' });
+			saveAs(blob, $scope.newRouterName);
+        }
+
+		$scope.downloadPart = function (part) {
+			var linkName = part.link.__data__.source.name + 'additional.conf';
+			var blob = new Blob([part.output], { type: 'text/plain;charset=utf-16' });
+			saveAs(blob, linkName);
+		}
+
+		$scope.done = function () {
+	        dialog.close();
+		}
+  };
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.form-controller.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.form-controller.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.form-controller.js
new file mode 100644
index 0000000..19af366
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.form-controller.js
@@ -0,0 +1,73 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+  'use strict';
+
+  angular
+    .module('horizon.dashboard.dispatch.topology')
+    .controller('horizon.dashboard.dispatch.topology.TopologyFormController', TopologyFormController);
+
+  TopologyFormController.$inject = [
+    '$scope',
+    'horizon.dashboard.dispatch.comService'
+  ];
+
+  function TopologyFormController($scope, QDRService) {
+    var fctrl = this;
+		$scope.attributes = []
+    var nameTemplate = '<div title="{{row.entity.description}}" class="ngCellText"><span>{{row.entity.attributeName}}</span></div>';
+    var valueTemplate = '<div title="{{row.entity.attributeValue}}" class="ngCellText"><span>{{row.entity.attributeValue}}</span></div>';
+    $scope.topoGridOptions = {
+      data: 'attributes',
+			enableColumnResize: true,
+			multiSelect: false,
+      columnDefs: [
+        {
+          field: 'attributeName',
+//          cellTemplate: nameTemplate,
+          displayName: 'Attribute'
+        },
+        {
+          field: 'attributeValue',
+//          cellTemplate: valueTemplate,
+          displayName: 'Value'
+        }
+      ]
+    };
+		$scope.form = ''
+		$scope.$on('showEntityForm', function (event, args) {
+			var attributes = args.attributes;
+			var entityTypes = QDRService.schema.entityTypes[args.entity].attributes;
+			attributes.forEach( function (attr) {
+				if (entityTypes[attr.attributeName] && entityTypes[attr.attributeName].description)
+					attr.description = entityTypes[attr.attributeName].description
+			})
+			$scope.attributes = attributes;
+			$scope.form = args.entity;
+		})
+		$scope.$on('showAddForm', function (event) {
+			$scope.form = 'add';
+		})
+	}
+
+  return QDR;
+}(QDR || {}));
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.module.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.module.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.module.js
new file mode 100644
index 0000000..e5a5242
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/topology.module.js
@@ -0,0 +1,112 @@
+/*
+ * Licensed 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.
+ */
+(function () {
+  'use strict';
+
+  angular
+    .module('horizon.dashboard.dispatch.topology', [])
+    .config(config)
+    .run(addTemplates)
+
+  config.$inject = [
+    '$provide',
+    '$windowProvider'
+  ];
+
+  addTemplates.$inject = [
+    '$templateCache',
+  ];
+
+  /**
+   * @name config
+   * @param {Object} $provide
+   * @param {Object} $windowProvider
+   * @description Base path for the overview code
+   * @returns {undefined} No return value
+   */
+  function config($provide, $windowProvider) {
+    var path = $windowProvider.$get().STATIC_URL + 'dashboard/dispatch/topology/';
+    $provide.constant('horizon.dashboard.dispatch.topology.basePath', path);
+  }
+
+  function addTemplates($templateCache) {
+    $templateCache.put("dispatch/topology.html",
+      "<div class=\"qdrTopology\" ng-controller=\"horizon.dashboard.dispatch.topology.TopologyController as ctrl\">" +
+      "    <div>" +
+      "<!--" +
+      "        <ul class=\"nav nav-tabs ng-scope qdrTopoModes\">" +
+      "            <li ng-repeat=\"mode in modes\" ng-class=\"{active : isModeActive(mode.name), 'pull-right' : isRight(mode)}\" ng-click=\"selectMode('{{mode.name}}')\" >" +
+      "                <a data-placement=\"bottom\" class=\"ng-binding\"> {{mode.name}} </a></li>" +
+      "        </ul>" +
+      "-->" +
+      "        <div id=\"topology\" ng-show=\"mode == 'Diagram'\"><!-- d3 toplogy here --></div>" +
+      "        <div id=\"geology\" ng-show=\"mode == 'Globe'\"><!-- d3 globe here --></div>" +
+      "        <div id=\"crosssection\"><!-- d3 pack here --></div>" +
+      "        <!-- <div id=\"addRouter\" ng-show=\"mode == 'Add Node'\"></div> -->" +
+      "        <div id=\"node_context_menu\" class=\"contextMenu\">" +
+      "            <ul>" +
+      "                <li class=\"na\" ng-class=\"{new: contextNode.cls == 'temp'}\" ng-click=\"addingNode.trigger = 'editNode'\">Edit...</li>" +
+      "                <li class=\"na\" ng-class=\"{adding: addingNode.step > 0}\" ng-click=\"addingNode.step = 0\">Cancel add</li>" +
+      "                <li class=\"context-separator\"></li>" +
+      "                <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_context_menu\" class=\"contextMenu\">" +
+      "            <ul>" +
+      "                <li ng-click=\"addingNode.step = 2\">Add a new router</li>" +
+      "            </ul>" +
+      "        </div>" +
+      "        <div id=\"link_context_menu\" class=\"contextMenu\">" +
+      "            <ul>" +
+      "                <li ng-click=\"reverseLink()\">Reverse connection direction</li>" +
+      "                <li ng-click=\"removeLink()\">Remove connection</li>" +
+      "            </ul>" +
+      "        </div>" +
+      "        <div id=\"svg_legend\"></div>" +
+      "        <div id=\"multiple_details\">" +
+      "            <h4 class=\"grid-title\">Connections</h4>" +
+      "            <div class=\"grid\" ui-grid=\"multiDetails\" ui-grid-selection></div>" +
+      "         </div>" +
+      "        <div id=\"link_details\">" +
+      "            <h4 class=\"grid-title\">Links</h4>" +
+      "            <div class=\"grid\" ui-grid=\"linkDetails\" ui-grid-selection></div>" +
+      "        </div>" +
+      "    </div>" +
+      "    <div ng-controller=\"horizon.dashboard.dispatch.topology.TopologyFormController as fctrl\">" +
+      "        <div id=\"topologyForm\" ng-class=\"{selected : isSelected()}\">" +
+      "            <!-- <div ng-repeat=\"form in forms\" ng-show=\"isVisible(form)\" ng-class='{selected : isSelected(form)}'> -->" +
+      "            <div ng-if=\"form == 'router'\">" +
+      "                <h3>Router Info</h3>" +
+      "                <div class=\"grid\" ui-grid=\"topoGridOptions\"></div>" +
+      "            </div>" +
+      "            <div ng-if=\"form == 'connection'\">" +
+      "                <h3>Connection Info</h3>" +
+      "                <div class=\"grid\" ui-grid=\"topoGridOptions\"></div>" +
+      "            </div>" +
+      "            <div id=\"addNodeForm\" ng-show=\"form == 'add'\">" +
+      "                <h3>Add a new router</h3>" +
+      "                <ul>" +
+      "                    <li>Click on an existing router to create a connection to the new router</li>" +
+      "                    <li>Double-click on the new router to <button ng-click=\"editNewRouter()\">edit</button> its properties</li>" +
+      "                    <li ng-show=\"addingNode.hasLink\" >Right-click on a new connection to edit its properties</li>" +
+      "                </ul>" +
+      "                <button ng-click=\"cancel()\">Cancel</button>" +
+      "            </div>" +
+      "        </div>" +
+      "    </div>" +
+      "</div>"
+    );
+  }
+})();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[10/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
DISPATCH-531 Initial version of openstack horizon plugin


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/0c58c381
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/0c58c381
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/0c58c381

Branch: refs/heads/master
Commit: 0c58c3814866cbe60f13ff69ad73c74a4d8692aa
Parents: 9790303
Author: Ernest Allen <ea...@redhat.com>
Authored: Wed Oct 19 08:48:34 2016 -0400
Committer: Ernest Allen <ea...@redhat.com>
Committed: Wed Oct 19 08:48:34 2016 -0400

----------------------------------------------------------------------
 console/dispatch-dashboard/MANIFEST.in          |     3 +
 console/dispatch-dashboard/README.rst           |    41 +
 console/dispatch-dashboard/dispatch/__init__.py |     0
 .../dispatch-dashboard/dispatch/dashboard.py    |    23 +
 .../dispatch/overv/__init__.py                  |     0
 .../dispatch-dashboard/dispatch/overv/panel.py  |    20 +
 .../dispatch/overv/templates/overv/index.html   |    13 +
 .../dispatch-dashboard/dispatch/overv/tests.py  |    19 +
 .../dispatch-dashboard/dispatch/overv/urls.py   |    20 +
 .../dispatch-dashboard/dispatch/overv/views.py  |    22 +
 .../static/dashboard/dispatch/connect.json      |     2 +
 .../dashboard/dispatch/dispatch.comService.js   |   935 +
 .../dashboard/dispatch/dispatch.module.js       |   256 +
 .../static/dashboard/dispatch/dispatch.scss     |  2135 ++
 .../dashboard/dispatch/jquery.dynatree.min.js   |     4 +
 .../static/dashboard/dispatch/lib/d3.v3.min.js  |     5 +
 .../static/dashboard/dispatch/lib/rhea-min.js   |     4 +
 .../static/dashboard/dispatch/lib/slider.js     |   233 +
 .../dashboard/dispatch/lib/tooltipsy.min.js     |    20 +
 .../static/dashboard/dispatch/lib/ui-grid.js    | 28540 +++++++++++++++++
 .../dispatch/overv/overview.controller.js       |  1428 +
 .../dashboard/dispatch/overv/overview.module.js |   178 +
 .../dashboard/dispatch/qdrChartService.js       |  1109 +
 .../dispatch/topology/config-file-header.html   |    17 +
 .../topology/download-dialog-template.html      |    23 +
 .../dispatch/topology/node-config-template.html |    51 +
 .../dispatch/topology/topology.controller.js    |  1703 +
 .../topology/topology.download-controller.js    |   150 +
 .../topology/topology.form-controller.js        |    73 +
 .../dispatch/topology/topology.module.js        |   112 +
 .../topology/topology.node-controller.js        |   294 +
 .../dispatch/templates/dispatch/base.html       |    10 +
 .../dispatch/topology/__init__.py               |     0
 .../dispatch/topology/panel.py                  |    20 +
 .../topology/templates/topology/index.html      |    35 +
 .../dispatch/topology/tests.py                  |    19 +
 .../dispatch/topology/urls.py                   |    20 +
 .../dispatch/topology/views.py                  |    22 +
 .../enabled/_4000_dispatch.py                   |    33 +
 .../enabled/_4030_dispatch_overv_panel.py       |     9 +
 .../enabled/_4050_dispatch_topology_panel.py    |     9 +
 console/dispatch-dashboard/setup.py             |    42 +
 42 files changed, 37652 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/MANIFEST.in
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/MANIFEST.in b/console/dispatch-dashboard/MANIFEST.in
new file mode 100644
index 0000000..1d1b591
--- /dev/null
+++ b/console/dispatch-dashboard/MANIFEST.in
@@ -0,0 +1,3 @@
+include setup.py
+
+recursive-include dispatch *

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/README.rst
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/README.rst b/console/dispatch-dashboard/README.rst
new file mode 100644
index 0000000..ac3b17c
--- /dev/null
+++ b/console/dispatch-dashboard/README.rst
@@ -0,0 +1,41 @@
+=========
+dispatch_dashboard
+=========
+
+Qpid Dispatch Router Horizon plugin
+
+Manual Installation
+-------------------
+
+Copy the contents of this directoty to /opt/stack/dispatch_plugin and setup the plugin::
+
+    cd /opt/stack/dispatch_plugin/
+    python setup.py sdist
+
+If needed, create a virtual environment and install Horizon dependencies::
+
+    cd /opt/stack/horizon
+    python tools/install_venv.py
+
+If needed, set up your ``local_settings.py`` file::
+
+    cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
+
+
+Install the dispatch dashboard in your horizon virtual environment::
+
+    ./tools/with_venv.sh pip install ../dispatch-plugin/dist/dispatch-0.0.1.tar.gz
+
+And enable it in Horizon::
+
+    cp ../dispatch-plugin/enabled/_4*.py openstack_dashboard/local/enabled
+
+If needed, compress the files::
+
+     ./tools/with-venv.sh python manage.py compress
+
+Run a server in the virtual environment::
+
+    ./tools/with-venv.sh python manage.py runserver 0.0.0.0:8080
+
+The horizon dashboard will be available in your browser at http://localhost:8080/

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/__init__.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/__init__.py b/console/dispatch-dashboard/dispatch/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/dashboard.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/dashboard.py b/console/dispatch-dashboard/dispatch/dashboard.py
new file mode 100644
index 0000000..9fad953
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/dashboard.py
@@ -0,0 +1,23 @@
+# Licensed 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.
+
+from django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+
+class Dispatch(horizon.Dashboard):
+    name = _("Qpid Dispatch")
+    slug = "dispatch"
+    default_panel = 'overv'  # slug of the dashboard's default panel.
+
+horizon.register(Dispatch)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/overv/__init__.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/overv/__init__.py b/console/dispatch-dashboard/dispatch/overv/__init__.py
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/overv/panel.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/overv/panel.py b/console/dispatch-dashboard/dispatch/overv/panel.py
new file mode 100644
index 0000000..315c7e0
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/overv/panel.py
@@ -0,0 +1,20 @@
+# Licensed 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.
+
+from django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+
+class Overv(horizon.Panel):
+    name = _("Overview")
+    slug = "overv"

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/overv/templates/overv/index.html
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/overv/templates/overv/index.html b/console/dispatch-dashboard/dispatch/overv/templates/overv/index.html
new file mode 100644
index 0000000..afe47bd
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/overv/templates/overv/index.html
@@ -0,0 +1,13 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Overv" %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Overview") %}
+{% endblock page_header %}
+
+{% block main %}
+  <ng-include src="'dispatch/overview.html'"></ng-include>
+{% endblock %}
+
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/overv/tests.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/overv/tests.py b/console/dispatch-dashboard/dispatch/overv/tests.py
new file mode 100644
index 0000000..47816a3
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/overv/tests.py
@@ -0,0 +1,19 @@
+# Licensed 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.
+
+from horizon.test import helpers as test
+
+
+class OvervTests(test.TestCase):
+    # Unit tests for overv.
+    def test_me(self):
+        self.assertTrue(1 + 1 == 2)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/overv/urls.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/overv/urls.py b/console/dispatch-dashboard/dispatch/overv/urls.py
new file mode 100644
index 0000000..6debf00
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/overv/urls.py
@@ -0,0 +1,20 @@
+# Licensed 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.
+
+from django.conf.urls import url
+
+from dispatch.overv import views
+
+
+urlpatterns = [
+    url(r'^$', views.IndexView.as_view(), name='index'),
+]

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/overv/views.py
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/overv/views.py b/console/dispatch-dashboard/dispatch/overv/views.py
new file mode 100644
index 0000000..235a0d5
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/overv/views.py
@@ -0,0 +1,22 @@
+# Licensed 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.
+
+from horizon import views
+
+
+class IndexView(views.APIView):
+    # A very simple class-based view...
+    template_name = 'dispatch/overv/index.html'
+
+    def get_data(self, request, context, *args, **kwargs):
+        # Add data to the context here...
+        return context

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/connect.json
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/connect.json b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/connect.json
new file mode 100644
index 0000000..2be876d
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/connect.json
@@ -0,0 +1,2 @@
+)]}',
+{"address": "0.0.0.0", "port": 5673}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
new file mode 100644
index 0000000..ace792a
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
@@ -0,0 +1,935 @@
+/*
+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.
+*/
+
+(function() {
+    console.dump = function(object) {
+        if (window.JSON && window.JSON.stringify)
+            QDR.log.info(JSON.stringify(object,undefined,2));
+        else
+            console.log(object);
+    };
+})();
+
+var QDR = (function(QDR) {
+  'use strict';
+
+  // The QDR service handles the connection to
+  // the server in the background
+  angular
+    .module('horizon.dashboard.dispatch')
+    .factory('horizon.dashboard.dispatch.comService', QDRService);
+
+  QDRService.$inject = [
+    '$rootScope',
+    '$http',
+    '$timeout',
+    '$location',
+    'horizon.dashboard.dispatch.basePath',
+  ];
+
+  function QDRService($rootScope, $http, $timeout, $location, basePath) {
+    var self = {
+
+	  rhea: require("rhea"),
+
+      timeout: 10,
+      connectActions: [],
+      disconnectActions: [],
+      updatedActions: {},
+      stop: undefined,  // update interval handle
+
+      addConnectAction: function(action) {
+        if (angular.isFunction(action)) {
+          self.connectActions.push(action);
+        }
+      },
+      addDisconnectAction: function(action) {
+        if (angular.isFunction(action)) {
+          self.disconnectActions.push(action);
+        }
+      },
+      addUpdatedAction: function(key, action) {
+        if (angular.isFunction(action)) {
+            self.updatedActions[key] = action;
+        }
+      },
+      delUpdatedAction: function(key) {
+        if (key in self.updatedActions)
+            delete self.updatedActions[key];
+      },
+
+      executeConnectActions: function() {
+        self.connectActions.forEach(function(action) {
+          //QDR.log.debug("executing connect action " + action);
+			try {
+                action.apply();
+            } catch (e) {
+                // in case the page that registered the handler has been unloaded
+            }
+        });
+        self.connectActions = [];
+
+      },
+      executeDisconnectActions: function() {
+        self.disconnectActions.forEach(function(action) {
+			try {
+                action.apply();
+            } catch (e) {
+                // in case the page that registered the handler has been unloaded
+            }
+        });
+        self.disconnectActions = [];
+      },
+      executeUpdatedActions: function() {
+        for (var action in self.updatedActions) {
+			try {
+                self.updatedActions[action].apply();
+            } catch (e) {
+                delete self.updatedActions[action]
+            }
+        }
+      },
+	redirectWhenConnected: function (org) {
+		//$location.path(basePath + "/connect")
+		//$location.search('org', org);
+           window.location.replace("/connect/");
+	},
+
+      notifyTopologyDone: function() {
+        //QDR.log.debug("got Toplogy done notice");
+
+        if (!angular.isDefined(self.schema))
+            return;
+        else if (self.topology._gettingTopo)
+            return;
+        if (!self.gotTopology) {
+            QDR.log.debug("topology was just initialized");
+            self.gotTopology = true;
+            self.executeConnectActions();
+            $rootScope.$apply();
+        } else {
+            //QDR.log.debug("topology model was just updated");
+            self.executeUpdatedActions();
+        }
+
+      },
+      /**
+       * @property options
+       * Holds a reference to the connection options when
+       * a connection is started
+       */
+      options: undefined,
+
+      /*
+       * @property message
+       * The proton message that is used to send commands
+       * and receive responses
+       */
+		sender: undefined,
+		receiver: undefined,
+		sendable: false,
+
+      schema: undefined,
+
+      toAddress: undefined,
+      connected: false,
+      gotTopology: false,
+      errorText: undefined,
+	  connectionError: undefined,
+
+      isConnected: function() {
+        return self.connected;
+      },
+
+    correlator: {
+        _objects: {},
+        _corremationID: 0,
+
+        corr: function () {
+            var id = ++this._corremationID + "";
+			this._objects[id] = {resolver: null}
+            return id;
+        },
+        request: function() {
+            //QDR.log.debug("correlator:request");
+            return this;
+        },
+        then: function(id, resolver, error) {
+            //QDR.log.debug("registered then resolver for correlationID: " + id);
+			if (error) {
+	            delete this._objects[id];
+				return;
+			}
+            this._objects[id].resolver = resolver;
+        },
+        // called by receiver's on('message') handler when a response arrives
+        resolve: function(context) {
+			var correlationID = context.message.properties.correlation_id;
+            this._objects[correlationID].resolver(context.message.body, context);
+            delete this._objects[correlationID];
+        }
+    },
+
+    onSubscription: function() {
+        self.getSchema();
+     },
+
+    startUpdating: function () {
+        self.stopUpdating();
+        QDR.log.info("startUpdating called")
+        self.topology.get();
+        self.stop = setInterval(function() {
+            self.topology.get();
+        }, 2000);
+    },
+    stopUpdating: function () {
+        if (angular.isDefined(self.stop)) {
+            QDR.log.info("stopUpdating called")
+            clearInterval(self.stop);
+            self.stop = undefined;
+        }
+    },
+
+      initProton: function() {
+        //self.loadConnectOptions()
+      },
+      cleanUp: function() {
+      },
+      error: function(line) {
+        if (line.num) {
+          QDR.log.debug("error - num: ", line.num, " message: ", line.message);
+        } else {
+          QDR.log.debug("error - message: ", line.message);
+        }
+      },
+      disconnected: function(line) {
+        QDR.log.debug("Disconnected from QDR server");
+        self.executeDisconnectActions();
+      },
+
+      nameFromId: function (id) {
+		    return id.split('/')[3];
+      },
+
+      humanify: function (s) {
+          if (!s || s.length === 0)
+			return s;
+          var t = s.charAt(0).toUpperCase() + s.substr(1).replace(/[A-Z]/g, ' $&');
+          return t.replace(".", " ");
+      },
+	  pretty: function(v) {
+    	var formatComma = d3.format(",");
+		if (!isNaN(parseFloat(v)) && isFinite(v))
+			return formatComma(v);
+		return v;
+	  },
+
+      nodeNameList: function() {
+        var nl = [];
+        // if we are in the middel of updating the topology
+        // then use the last known node info
+        var ni = self.topology._nodeInfo;
+        if (self.topology._gettingTopo)
+            ni = self.topology._lastNodeInfo;
+		for (var id in ni) {
+            nl.push(self.nameFromId(id));
+        }
+        return nl.sort();
+      },
+
+      nodeIdList: function() {
+        var nl = [];
+        // if we are in the middel of updating the topology
+        // then use the last known node info
+        var ni = self.topology._nodeInfo;
+        if (self.topology._gettingTopo)
+            ni = self.topology._lastNodeInfo;
+		for (var id in ni) {
+            nl.push(id);
+        }
+        return nl.sort();
+      },
+
+      nodeList: function () {
+        var nl = [];
+        var ni = self.topology._nodeInfo;
+        if (self.topology._gettingTopo)
+            ni = self.topology._lastNodeInfo;
+		for (var id in ni) {
+            nl.push({name: self.nameFromId(id), id: id});
+        }
+        return nl;
+      },
+
+      // given an attribute name array, find the value at the same index in the values array
+      valFor: function (aAr, vAr, key) {
+          var idx = aAr.indexOf(key);
+          if ((idx > -1) && (idx < vAr.length)) {
+              return vAr[idx];
+          }
+          return null;
+      },
+
+		isArtemis: function (d) {
+			return d.nodeType ==='on-demand' && !d.properties.product;
+		},
+
+		isQpid: function (d) {
+			return d.nodeType ==='on-demand' && (d.properties && d.properties.product === 'qpid-cpp');
+		},
+
+		isAConsole: function (properties, connectionId, nodeType, key) {
+			return self.isConsole({properties: properties, connectionId: connectionId, nodeType: nodeType, key: key})
+		},
+		isConsole: function (d) {
+			// use connection properties if available
+			if (d && d['properties'] && d['properties']['console_identifier'] == 'Dispatch console')
+				return true;
+			return false;
+		},
+
+		flatten: function (attributes, result) {
+			var flat = {}
+			attributes.forEach( function (attr, i) {
+				if (result && result.length > i)
+					flat[attr] = result[i]
+			})
+			return flat;
+		},
+		isConsoleLink: function (link) {
+			// find the connection for this link
+			var conns = self.topology.nodeInfo()[link.nodeId]['.connection']
+			var connIndex = conns.attributeNames.indexOf("identity")
+			var linkCons = conns.results.filter ( function (conn) {
+				return conn[connIndex] === link.connectionId;
+			})
+			var conn = self.flatten(conns.attributeNames, linkCons[0]);
+
+			return self.isConsole(conn)
+		},
+
+		quiesceLink: function (nodeId, name) {
+			function gotMethodResponse (nodeName, entity, response, context) {
+				var statusCode = context.message.application_properties.statusCode;
+				if (statusCode < 200 || statusCode >= 300) {
+					Core.notification('error', context.message.application_properties.statusDescription);
+				}
+			}
+			var attributes = {adminStatus: 'disabled', name: name};
+			self.sendMethod(nodeId, "router.link", attributes, "UPDATE", undefined, gotMethodResponse)
+		},
+
+    connectionOptions: {address: '0.0.0.0', port: 5673},
+    loadConnectOptions: function (callback) {
+      $http.get(basePath + 'connect.json').
+        success(function(data, status, headers, config) {
+        //QDR.log.debug("got connect info from file")
+        //console.dump(data)
+          self.connectionOptions = data;
+          if (callback)
+            callback()
+        }).
+        error(function(data, status, headers, config) {
+        //QDR.log.debug("did not get connect info from file")
+        //console.dump(status)
+          if (callback)
+            callback()
+        });
+    },
+
+		addr_text: function (addr) {
+	        if (!addr)
+	            return "-"
+	        if (addr[0] == 'M')
+	            return addr.substring(2)
+	        else
+	            return addr.substring(1)
+		},
+		addr_class: function (addr) {
+			if (!addr) return "-"
+	        if (addr[0] == 'M')  return "mobile"
+	        if (addr[0] == 'R')  return "router"
+	        if (addr[0] == 'A')  return "area"
+	        if (addr[0] == 'L')  return "local"
+	        if (addr[0] == 'C')  return "link-incoming"
+	        if (addr[0] == 'D')  return "link-outgoing"
+	        if (addr[0] == 'T')  return "topo"
+	        return "unknown: " + addr[0]
+		},
+		identity_clean: function (identity) {
+	        if (!identity)
+	            return "-"
+	        var pos = identity.indexOf('/')
+	        if (pos >= 0)
+	            return identity.substring(pos + 1)
+	        return identity
+		},
+
+      /*
+       * send the management messages that build up the topology
+       *
+       *
+       */
+      topology: {
+        _gettingTopo: false,
+        _nodeInfo: {},
+        _lastNodeInfo: {},
+        _expected: {},
+        _timerHandle: null,
+
+        nodeInfo: function () {
+            return this._gettingTopo ? this._lastNodeInfo : this._nodeInfo;
+        },
+
+        get: function () {
+            if (this._gettingTopo)
+                return;
+            if (!self.connected) {
+				QDR.log.debug("topology get failed because !self.connected")
+                return;
+            }
+            this._lastNodeInfo = angular.copy(this._nodeInfo);
+            this._gettingTopo = true;
+
+            self.errorText = undefined;
+            this.cleanUp(this._nodeInfo);
+            this._nodeInfo = {};
+            this._expected = {};
+
+            // get the list of nodes to query.
+            // once this completes, we will get the info for each node returned
+            self.getRemoteNodeInfo( function (response, context) {
+                //QDR.log.debug("got remote node list of ");
+                //console.dump(response);
+                if( Object.prototype.toString.call( response ) === '[object Array]' ) {
+					if (response.length === 0) {
+						// there is only one router, get its node id from the reeciiver
+						//"amqp:/_topo/0/Router.A/temp.aSO3+WGaoNUgGVx"
+						var address = context.receiver.remote.attach.source.address;
+						var addrParts = address.split('/')
+						addrParts.splice(addrParts.length-1, 1, '$management')
+						response = [addrParts.join('/')]
+					}
+                    // we expect a response for each of these nodes
+                    self.topology.wait(self.timeout);
+                    for (var i=0; i<response.length; ++i) {
+                        self.makeMgmtCalls(response[i]);
+                    }
+                };
+            });
+        },
+
+        cleanUp: function (obj) {
+            //if (obj)
+            //    delete obj;
+        },
+        wait: function (timeout) {
+            this.timerHandle = setTimeout(this.timedOut, timeout * 1000);
+         },
+        timedOut: function () {
+        // a node dropped out. this happens when the get-mgmt-nodex
+        // results contains more nodes than actually respond within
+        // the timeout. However, if the responses we get don't contain
+        // the missing node, assume we are done.
+            QDR.log.info("timed out waiting for management responses");
+            // note: can't use 'this' in a timeout handler
+            self.topology.miniDump("state at timeout");
+            // check if _nodeInfo is consistent
+            if (self.topology.isConsistent()) {
+                //TODO: notify controllers which node was dropped
+                // so they can keep an event log
+                self.topology.ondone();
+                return;
+            }
+            self.topology.onerror(Error("Timed out waiting for management responses"));
+        },
+        isConsistent: function () {
+            // see if the responses we have so far reference any nodes
+            // for which we don't have a response
+            var gotKeys = {};
+            for (var id in this._nodeInfo) {
+                var onode = this._nodeInfo[id];
+                var conn = onode['.connection'];
+                // get list of node names in the connection data
+                if (conn) {
+                    var containerIndex = conn.attributeNames.indexOf('container');
+                    var connectionResults = conn.results;
+                    if (containerIndex >= 0)
+                        for (var j=0; j < connectionResults.length; ++j) {
+                            // inter-router connection to a valid dispatch connection name
+                            gotKeys[connectionResults[j][containerIndex]] = ""; // just add the key
+                        }
+                }
+            }
+            // gotKeys now contains all the container names that we have received
+            // Are any of the keys that are still expected in the gotKeys list?
+            var keys = Object.keys(gotKeys);
+            for (var id in this._expected) {
+                var key = self.nameFromId(id);
+                if (key in keys)
+                    return false;
+            }
+            return true;
+        },
+
+        addNodeInfo: function (id, entity, values) {
+            // save the results in the nodeInfo object
+            if (id) {
+                if (!(id in self.topology._nodeInfo)) {
+                    self.topology._nodeInfo[id] = {};
+                }
+                self.topology._nodeInfo[id][entity] = values;
+            }
+
+            // remove the id / entity from _expected
+            if (id in self.topology._expected) {
+                var entities = self.topology._expected[id];
+                var idx = entities.indexOf(entity);
+                if (idx > -1) {
+                    entities.splice(idx, 1);
+                    if (entities.length == 0)
+                        delete self.topology._expected[id];
+                }
+            }
+            // see if the expected obj is empty
+            if (Object.getOwnPropertyNames(self.topology._expected).length == 0)
+                self.topology.ondone();
+            self.topology.cleanUp(values);
+        },
+        expect: function (id, key) {
+            if (!key || !id)
+                return;
+            if (!(id in this._expected))
+                this._expected[id] = [];
+            if (this._expected[id].indexOf(key) == -1)
+                this._expected[id].push(key);
+        },
+        ondone: function () {
+            clearTimeout(this.timerHandle);
+            this._gettingTopo = false;
+            //this.miniDump();
+            //this.dump();
+            self.notifyTopologyDone();
+         },
+         dump: function (prefix) {
+            if (prefix)
+                QDR.log.info(prefix);
+            QDR.log.info("---");
+            for (var key in this._nodeInfo) {
+                QDR.log.info(key);
+                console.dump(this._nodeInfo[key]);
+                QDR.log.info("---");
+            }
+            QDR.log.debug("was still expecting:");
+            console.dump(this._expected);
+        },
+         miniDump: function (prefix) {
+            if (prefix)
+                QDR.log.info(prefix);
+            QDR.log.info("---");
+            console.dump(Object.keys(this._nodeInfo));
+            QDR.log.info("---");
+        },
+        onerror: function (err) {
+            this._gettingTopo = false;
+            QDR.log.debug("Err:" + err);
+            self.executeDisconnectActions();
+
+        }
+
+      },
+
+      getRemoteNodeInfo: function (callback) {
+	 	//QDR.log.debug("getRemoteNodeInfo called");
+        var ret;
+        // first get the list of remote node names
+	 	self.correlator.request(
+                ret = self.sendMgmtQuery('GET-MGMT-NODES')
+            ).then(ret.id, function(response, context) {
+                callback(response, context);
+                self.topology.cleanUp(response);
+            }, ret.error);
+      },
+
+      makeMgmtCalls: function (id) {
+            var keys = [".router", ".connection", ".container", ".router.node", ".listener", ".router.link"];
+            $.each(keys, function (i, key) {
+                self.topology.expect(id, key);
+                self.getNodeInfo(id, key, [], self.topology.addNodeInfo);
+            });
+      },
+
+      getNodeInfo: function (nodeName, entity, attrs, callback) {
+        //QDR.log.debug("getNodeInfo called with nodeName: " + nodeName + " and entity " + entity);
+        var ret;
+        self.correlator.request(
+            ret = self.sendQuery(nodeName, entity, attrs)
+        ).then(ret.id, function(response) {
+            callback(nodeName, entity, response);
+            //self.topology.addNodeInfo(nodeName, entity, response);
+            //self.topology.cleanUp(response);
+        }, ret.error);
+      },
+
+		getMultipleNodeInfo: function (nodeNames, entity, attrs, callback, selectedNodeId, aggregate) {
+			if (!angular.isDefined(aggregate))
+				aggregate = true;
+			var responses = {};
+			var gotNodesResult = function (nodeName, dotentity, response) {
+				responses[nodeName] = response;
+				if (Object.keys(responses).length == nodeNames.length) {
+					if (aggregate)
+						self.aggregateNodeInfo(nodeNames, entity, selectedNodeId, responses, callback);
+					else {
+						callback(nodeNames, entity, responses)
+					}
+				}
+			}
+
+			nodeNames.forEach( function (id) {
+	            self.getNodeInfo(id, '.'+entity, attrs, gotNodesResult);
+	        })
+			//TODO: implement a timeout in case not all requests complete
+		},
+
+		aggregateNodeInfo: function (nodeNames, entity, selectedNodeId, responses, callback) {
+			//QDR.log.debug("got all results for  " + entity);
+			// aggregate the responses
+			var newResponse = {};
+			var thisNode = responses[selectedNodeId];
+			newResponse['attributeNames'] = thisNode.attributeNames;
+			newResponse['results'] = thisNode.results;
+			newResponse['aggregates'] = [];
+			for (var i=0; i<thisNode.results.length; ++i) {
+				var result = thisNode.results[i];
+				var vals = [];
+				result.forEach( function (val) {
+					vals.push({sum: val, detail: []})
+				})
+				newResponse.aggregates.push(vals);
+			}
+			var nameIndex = thisNode.attributeNames.indexOf("name");
+			var ent = self.schema.entityTypes[entity];
+			var ids = Object.keys(responses);
+			ids.sort();
+			ids.forEach( function (id) {
+				var response = responses[id];
+				var results = response.results;
+				results.forEach( function (result) {
+					// find the matching result in the aggregates
+					var found = newResponse.aggregates.some( function (aggregate, j) {
+						if (aggregate[nameIndex].sum === result[nameIndex]) {
+							// result and aggregate are now the same record, add the graphable values
+							newResponse.attributeNames.forEach( function (key, i) {
+								if (ent.attributes[key] && ent.attributes[key].graph) {
+									if (id != selectedNodeId)
+										aggregate[i].sum += result[i];
+								}
+								aggregate[i].detail.push({node: self.nameFromId(id)+':', val: result[i]})
+							})
+							return true; // stop looping
+						}
+						return false; // continute looking for the aggregate record
+					})
+					if (!found) {
+						// this attribute was not found in the aggregates yet
+						// because it was not in the selectedNodeId's results
+						var vals = [];
+						result.forEach( function (val) {
+							vals.push({sum: val, detail: [{node: self.nameFromId(id), val: val}]})
+						})
+						newResponse.aggregates.push(vals)
+					}
+				})
+			})
+			callback(nodeNames, entity, newResponse);
+		},
+
+
+      getSchema: function () {
+        //QDR.log.debug("getting schema");
+        var ret;
+        self.correlator.request(
+            ret = self.sendMgmtQuery('GET-SCHEMA')
+        ).then(ret.id, function(response) {
+            //QDR.log.debug("Got schema response");
+			// remove deprecated
+			for (var entityName in response.entityTypes) {
+				var entity = response.entityTypes[entityName]
+				if (entity.deprecated) {
+					// deprecated entity
+				    delete response.entityTypes[entityName]
+				} else {
+					for (var attributeName in entity.attributes) {
+						var attribute = entity.attributes[attributeName]
+						if (attribute.deprecated) {
+							// deprecated attribute
+							delete response.entityTypes[entityName].attributes[attributeName]
+						}
+					}
+				}
+			}
+			self.schema = response;
+	        self.topology.get();
+        }, ret.error);
+      },
+
+      getNodeInfo: function (nodeName, entity, attrs, callback) {
+        //QDR.log.debug("getNodeInfo called with nodeName: " + nodeName + " and entity " + entity);
+        var ret;
+        self.correlator.request(
+            ret = self.sendQuery(nodeName, entity, attrs)
+        ).then(ret.id, function(response) {
+            callback(nodeName, entity, response);
+            //self.topology.addNodeInfo(nodeName, entity, response);
+            //self.topology.cleanUp(response);
+        }, ret.error);
+      },
+
+	sendMethod: function (nodeId, entity, attrs, operation, props, callback) {
+		var ret;
+		self.correlator.request(
+			ret = self._sendMethod(nodeId, entity, attrs, operation, props)
+		).then(ret.id, function (response, context) {
+				callback(nodeId, entity, response, context);
+		}, ret.error);
+	},
+
+	_fullAddr: function (toAddr) {
+        var toAddrParts = toAddr.split('/');
+        if (toAddrParts.shift() != "amqp:") {
+            self.topology.error(Error("unexpected format for router address: " + toAddr));
+            return;
+        }
+        //var fullAddr =  self.toAddress + "/" + toAddrParts.join('/');
+        var fullAddr =  toAddrParts.join('/');
+		return fullAddr;
+	},
+
+	_sendMethod: function (toAddr, entity, attrs, operation, props) {
+		var fullAddr = self._fullAddr(toAddr);
+		var ret = {id: self.correlator.corr()};
+		if (!self.sender || !self.sendable) {
+			ret.error = "no sender"
+			return ret;
+		}
+		try {
+			var application_properties = {
+				operation:  operation
+			}
+			if (entity) {
+				var ent = self.schema.entityTypes[entity];
+				var fullyQualifiedType = ent ? ent.fullyQualifiedType : entity;
+				application_properties.type = fullyQualifiedType || entity;
+			}
+			if (attrs.name)
+				application_properties.name = attrs.name;
+			if (props) {
+				jQuery.extend(application_properties, props);
+			}
+			var msg = {
+	                body: attrs,
+	                properties: {
+	                    to:                     fullAddr,
+                        reply_to:               self.receiver.remote.attach.source.address,
+	                    correlation_id:         ret.id
+	                },
+	                application_properties: application_properties
+            }
+            self.sender.send( msg );
+			console.dump("------- method called -------")
+            console.dump (msg)
+		}
+		catch (e) {
+			error = "error sending: " + e;
+			QDR.log.error(error)
+			ret.error = error;
+		}
+		return ret;
+	},
+
+    sendQuery: function(toAddr, entity, attrs, operation) {
+        operation = operation || "QUERY"
+		var fullAddr = self._fullAddr(toAddr);
+
+		var body;
+        if (attrs)
+            body = {
+                    "attributeNames": attrs,
+            }
+        else
+            body = {
+                "attributeNames": [],
+            }
+		if (entity[0] === '.')
+			entity = entity.substr(1, entity.length-1)
+		var prefix = "org.apache.qpid.dispatch."
+		var configs = ["address", "autoLink", "linkRoute"]
+		if (configs.indexOf(entity) > -1)
+			prefix += "router.config."
+		return self._send(body, fullAddr, operation, prefix + entity);
+    },
+
+    sendMgmtQuery: function (operation) {
+		return self._send([], "/$management", operation);
+    },
+
+	_send: function (body, to, operation, entityType) {
+		var ret = {id: self.correlator.corr()};
+		if (!self.sender || !self.sendable) {
+			ret.error = "no sender"
+			return ret;
+		}
+		try {
+			var application_properties = {
+				operation:  operation,
+                type:       "org.amqp.management",
+                name:       "self"
+            };
+			if (entityType)
+                application_properties.entityType = entityType;
+
+	        self.sender.send({
+	                body: body,
+	                properties: {
+	                    to:                     to,
+                        reply_to:               self.receiver.remote.attach.source.address,
+	                    correlation_id:         ret.id
+	                },
+	                application_properties: application_properties
+            })
+		}
+		catch (e) {
+			error = "error sending: " + e;
+			QDR.log.error(error)
+			ret.error = error;
+		}
+		return ret;
+	},
+
+      disconnect: function() {
+        self.connection.close();
+		self.errorText = "Disconnected."
+      },
+
+      connect: function(overrideConnectOptions) {
+  			QDR.log.debug("****** calling rhea.connect ********")
+        var options = self.connectionOptions;
+        if (overrideConnectOptions)
+          options = overrideConnectOptions;
+        self.topologyInitialized = false;
+		    if (!self.connected) {
+			    var okay = {connection: false, sender: false, receiver: false}
+          var port = options.port || 5673;
+          var baseAddress = options.address + ':' + port;
+			    var ws = self.rhea.websocket_connect(WebSocket);
+			    self.toAddress = "amqp://" + baseAddress;
+			    self.connectionError = undefined;
+
+			var stop = function (context) {
+				//self.stopUpdating();
+				okay.sender = false;
+				okay.receiver = false;
+				okay.connected = false;
+				self.connected = false;
+				self.sender = null;
+				self.receiver = null;
+				self.sendable = false;
+				self.gotTopology = false;
+			}
+			var maybeStart = function () {
+				if (okay.connection && okay.sender && okay.receiver && self.sendable && !self.connected) {
+					QDR.log.info("okay to start")
+					self.connected = true;
+					self.connection = connection;
+					self.sender = sender;
+					self.receiver = receiver;
+					self.onSubscription();
+					self.gotTopology = false;
+				}
+			}
+			var onDisconnect = function () {
+				//QDR.log.warn("Disconnected");
+				self.connectionError = true;
+				stop();
+				self.executeDisconnectActions();
+			}
+
+			var connection;
+			try {
+QDR.log.debug("trying to connect to ws://" + baseAddress)
+                connection = self.rhea.connect({
+                    connection_details:ws('ws://' + baseAddress, ["binary", "base64", "AMQWSB10"]),
+                    reconnect:true,
+                    properties: {console_identifier: 'Dispatch console'}
+	            });
+			}
+			catch (e) {
+				QDR.log.debug("exception caught on connect")
+				self.errorText = "Connection failed"
+				onDisconnect();
+			}
+			if (!self.connectionError) {
+				connection.on('connection_open', function (context) {
+					QDR.log.debug("connection_opened")
+					okay.connection = true;
+					okay.receiver = false;
+					okay.sender = false;
+				})
+				connection.on('disconnected', function (context) {
+					QDR.log.debug("connection disconnected")
+					self.errorText = "Unable to connect"
+					onDisconnect();
+				})
+				connection.on('connection_close', function (context) {
+					QDR.log.debug("connection closed")
+					self.errorText = "Disconnected"
+					onDisconnect();
+				})
+
+				var sender = connection.open_sender();
+				sender.on('sender_open', function (context) {
+					QDR.log.debug("sender_opened")
+					okay.sender = true
+					maybeStart()
+				})
+				sender.on('sendable', function (context) {
+					//QDR.log.debug("sendable")
+					self.sendable = true;
+					maybeStart();
+				})
+
+				var receiver = connection.open_receiver({source: {dynamic: true}});
+				receiver.on('receiver_open', function (context) {
+					QDR.log.debug("receiver_opened")
+					okay.receiver = true;
+					maybeStart()
+				})
+				receiver.on("message", function (context) {
+					self.correlator.resolve(context);
+				});
+			}
+		}
+      }
+    }
+    return self;
+  };
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.module.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.module.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.module.js
new file mode 100644
index 0000000..48cc85f
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.module.js
@@ -0,0 +1,256 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ * @main QDR
+ *
+ * The main entry point for the QDR module
+ *
+ */
+var QDR = (function(QDR) {
+
+  /**
+   * @property pluginName
+   * @type {string}
+   *
+   * The name of this plugin
+   */
+  QDR.pluginName = "QDR";
+  QDR.pluginRoot = "";
+  QDR.isStandalone = true;
+  QDR.isHorizon = true;
+  QDR.offsetParent = ".col-xs-12";
+
+  /**
+   * @property log
+   * @type {Logging.Logger}
+   *
+   * This plugin's logger instance
+   */
+  //HIO QDR.log = Logger.get(QDR.pluginName);
+  /**
+   * @property templatePath
+   * @type {string}
+   *
+   * The top level path to this plugin's partials
+   */
+  QDR.srcBase = "plugin/";
+  QDR.templatePath = QDR.srcBase + "html/";
+  QDR.cssPath = QDR.srcBase + "css/";
+  /**
+   * @property SETTINGS_KEY
+   * @type {string}
+   *
+   * The key used to fetch our settings from local storage
+   */
+  QDR.SETTINGS_KEY = 'QDRSettings';
+  QDR.LAST_LOCATION = "QDRLastLocation";
+
+  /**
+   * @property module
+   * @type {object}
+   *
+   * This plugin's angularjs module instance
+   */
+  QDR.module = angular.module('horizon.dashboard.dispatch',
+    [
+    'ui.grid',
+    'ui.grid.resizeColumns',
+    'ui.grid.selection',
+    'ui.bootstrap',
+    'ui.slider',
+    'horizon.dashboard.dispatch.overv',
+    'horizon.dashboard.dispatch.topology'
+    ])
+
+  Core = {
+	  notification: function (severity, msg) {
+        $.notify(msg, severity);
+    }
+  }
+
+  QDR.module.config(['$provide', '$windowProvider',
+    function ($provide, $windowProvider) {
+      var path = $windowProvider.$get().STATIC_URL + 'dashboard/dispatch/';
+      $provide.constant('horizon.dashboard.dispatch.basePath', path);
+    }
+  ]);
+
+  QDR.module.config(['$compileProvider',
+    function($compileProvider) {
+      $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|blob):/);
+    }
+  ]);
+
+	QDR.module.filter('to_trusted', ['$sce', function($sce){
+          return function(text) {
+              return $sce.trustAsHtml(text);
+          };
+    }]);
+
+	QDR.module.filter('humanify', ['horizon.dashboard.dispatch.comService', function (QDRService) {
+		return function (input) {
+			return QDRService.humanify(input);
+		};
+	}]);
+
+	QDR.module.filter('Pascalcase', function () {
+		return function (str) {
+			if (!str)
+				return "";
+			return str.replace(/(\w)(\w*)/g,
+			function(g0,g1,g2){return g1.toUpperCase() + g2.toLowerCase();});
+		}
+	})
+
+    QDR.module.filter('safePlural', function () {
+	        return function (str) {
+				var es = ['x', 'ch', 'ss', 'sh']
+				for (var i=0; i<es.length; ++i) {
+					if (str.endsWith(es[i]))
+						return str + 'es'
+				}
+				if (str.endsWith('y'))
+					return str.substr(0, str.length-2) + 'ies'
+				if (str.endsWith('s'))
+					return str;
+				return str + 's'
+	        }
+	})
+
+	QDR.logger = function ($log) {
+		var log = $log;
+
+		this.debug = function (msg) { msg = "QDR: " + msg; log.debug(msg)};
+		this.error = function (msg) {msg = "QDR: " + msg; log.error(msg)}
+		this.info = function (msg) {msg = "QDR: " + msg; log.info(msg)}
+		this.warn = function (msg) {msg = "QDR: " + msg; log.warn(msg)}
+
+		return this;
+	}
+    // one-time initialization happens in the run function
+    // of our module
+  QDR.module.run(
+    ["$rootScope",
+    '$route',
+    '$timeout',
+    "$location",
+    "$log",
+    "horizon.dashboard.dispatch.comService",
+    "horizon.dashboard.dispatch.chartService",
+    function (
+      $rootScope,
+      $route,
+      $timeout,
+      $location,
+      $log,
+      QDRService,
+      QDRChartService) {
+        QDR.log = new QDR.logger($log);
+        QDR.log.info("*************creating Dispatch Console************");
+
+        var curPath = $location.path()
+        var org = curPath.substr(1)
+        if (org && org.length > 0 && org !== "connect") {
+        //  $location.search('org', org)
+        } else {
+        //  $location.search('org', null)
+        }
+
+        QDRService.initProton();
+        var settings = angular.fromJson(localStorage[QDR.SETTINGS_KEY]);
+        QDRService.addConnectAction(function() {
+          QDRChartService.init(); // initialize charting service after we are connected
+        });
+
+    if (settings && settings.autostart) {
+      QDRService.addDisconnectAction( function () {
+        $timeout(function () {
+          var lastLocation = localStorage[QDR.LAST_LOCATION] || "/overview";
+          org = lastLocation.substr(1)
+          //$location.path("/connect");
+          //$location.search('org', org)
+debugger;
+          window.location.replace("/dispatch/connect/");
+        })
+      })
+      QDRService.addConnectAction(function() {
+        var searchObject = $location.search();
+        // the redirect will be handled by QDRService when connected
+        if (searchObject.org) {
+          return;
+        }
+        // there was no org= parameter, so redirect to last known location
+        $timeout(function () {
+          var lastLocation = localStorage[QDR.LAST_LOCATION] || "/overview";
+          //$location.path(lastLocation);
+        })
+      });
+      QDRService.connect(settings);
+    } else {
+QDR.log.debug("QDR.module run called with location of " + $location.path());
+      $timeout(function () {
+        //$location.path('/connect')
+        //$location.search('org', org)
+//debugger;
+//        window.location.replace("/dispatch/connect/");
+      })
+    }
+
+    $rootScope.$on('$routeChangeSuccess', function() {
+      var path = $location.path();
+      if (path !== "/connect") {
+        localStorage[QDR.LAST_LOCATION] = path;
+      }
+    });
+  }]);
+
+	QDR.module.controller ("QDR.Core", function ($scope, $rootScope) {
+		$scope.alerts = [];
+		$scope.closeAlert = function(index) {
+            $scope.alerts.splice(index, 1);
+        };
+		$scope.$on('newAlert', function(event, data) {
+			$scope.alerts.push(data);
+			$scope.$apply();
+		});
+		$scope.$on("clearAlerts", function () {
+			$scope.alerts = [];
+			$scope.$apply();
+		})
+
+	})
+
+  return QDR;
+}(QDR || {}));
+
+var Folder = (function () {
+    function Folder(title) {
+        this.title = title;
+	this.children = [];
+	this.folder = true;
+    }
+    return Folder;
+})();
+var Leaf = (function () {
+    function Leaf(title) {
+        this.title = title;
+    }
+    return Leaf;
+})();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[05/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js
new file mode 100644
index 0000000..545ef70
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/ui-grid.js
@@ -0,0 +1,28540 @@
+/*!
+ * ui-grid - v3.2.9 - 2016-09-21
+ * Copyright (c) 2016 ; License: MIT 
+ */
+
+(function () {
+  'use strict';
+  angular.module('ui.grid.i18n', []);
+  angular.module('ui.grid', ['ui.grid.i18n']);
+})();
+(function () {
+  'use strict';
+
+  /**
+   * @ngdoc object
+   * @name ui.grid.service:uiGridConstants
+   * @description Constants for use across many grid features
+   *
+   */
+
+
+  angular.module('ui.grid').constant('uiGridConstants', {
+    LOG_DEBUG_MESSAGES: true,
+    LOG_WARN_MESSAGES: true,
+    LOG_ERROR_MESSAGES: true,
+    CUSTOM_FILTERS: /CUSTOM_FILTERS/g,
+    COL_FIELD: /COL_FIELD/g,
+    MODEL_COL_FIELD: /MODEL_COL_FIELD/g,
+    TOOLTIP: /title=\"TOOLTIP\"/g,
+    DISPLAY_CELL_TEMPLATE: /DISPLAY_CELL_TEMPLATE/g,
+    TEMPLATE_REGEXP: /<.+>/,
+    FUNC_REGEXP: /(\([^)]*\))?$/,
+    DOT_REGEXP: /\./g,
+    APOS_REGEXP: /'/g,
+    BRACKET_REGEXP: /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/,
+    COL_CLASS_PREFIX: 'ui-grid-col',
+    ENTITY_BINDING: '$$this',
+    events: {
+      GRID_SCROLL: 'uiGridScroll',
+      COLUMN_MENU_SHOWN: 'uiGridColMenuShown',
+      ITEM_DRAGGING: 'uiGridItemDragStart', // For any item being dragged
+      COLUMN_HEADER_CLICK: 'uiGridColumnHeaderClick'
+    },
+    // copied from http://www.lsauer.com/2011/08/javascript-keymap-keycodes-in-json.html
+    keymap: {
+      TAB: 9,
+      STRG: 17,
+      CAPSLOCK: 20,
+      CTRL: 17,
+      CTRLRIGHT: 18,
+      CTRLR: 18,
+      SHIFT: 16,
+      RETURN: 13,
+      ENTER: 13,
+      BACKSPACE: 8,
+      BCKSP: 8,
+      ALT: 18,
+      ALTR: 17,
+      ALTRIGHT: 17,
+      SPACE: 32,
+      WIN: 91,
+      MAC: 91,
+      FN: null,
+      PG_UP: 33,
+      PG_DOWN: 34,
+      UP: 38,
+      DOWN: 40,
+      LEFT: 37,
+      RIGHT: 39,
+      ESC: 27,
+      DEL: 46,
+      F1: 112,
+      F2: 113,
+      F3: 114,
+      F4: 115,
+      F5: 116,
+      F6: 117,
+      F7: 118,
+      F8: 119,
+      F9: 120,
+      F10: 121,
+      F11: 122,
+      F12: 123
+    },
+     /**
+     * @ngdoc object
+     * @name ASC
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
+     * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
+     * to configure the sorting direction of the column
+     */
+    ASC: 'asc',
+     /**
+     * @ngdoc object
+     * @name DESC
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_sort columnDef.sort} and
+     * {@link ui.grid.class:GridOptions.columnDef#properties_sortDirectionCycle columnDef.sortDirectionCycle}
+     * to configure the sorting direction of the column
+     */
+    DESC: 'desc',
+
+
+     /**
+     * @ngdoc object
+     * @name filter
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_filter columnDef.filter}
+     * to configure filtering on the column
+     *
+     * `SELECT` and `INPUT` are used with the `type` property of the filter, the rest are used to specify
+     * one of the built-in conditions.
+     *
+     * Available `condition` options are:
+     * - `uiGridConstants.filter.STARTS_WITH`
+     * - `uiGridConstants.filter.ENDS_WITH`
+     * - `uiGridConstants.filter.CONTAINS`
+     * - `uiGridConstants.filter.GREATER_THAN`
+     * - `uiGridConstants.filter.GREATER_THAN_OR_EQUAL`
+     * - `uiGridConstants.filter.LESS_THAN`
+     * - `uiGridConstants.filter.LESS_THAN_OR_EQUAL`
+     * - `uiGridConstants.filter.NOT_EQUAL`
+     * - `uiGridConstants.filter.STARTS_WITH`
+     *
+     *
+     * Available `type` options are:
+     * - `uiGridConstants.filter.SELECT` - use a dropdown box for the cell header filter field
+     * - `uiGridConstants.filter.INPUT` - use a text box for the cell header filter field
+     */
+    filter: {
+      STARTS_WITH: 2,
+      ENDS_WITH: 4,
+      EXACT: 8,
+      CONTAINS: 16,
+      GREATER_THAN: 32,
+      GREATER_THAN_OR_EQUAL: 64,
+      LESS_THAN: 128,
+      LESS_THAN_OR_EQUAL: 256,
+      NOT_EQUAL: 512,
+      SELECT: 'select',
+      INPUT: 'input'
+    },
+
+    /**
+     * @ngdoc object
+     * @name aggregationTypes
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used in {@link ui.grid.class:GridOptions.columnDef#properties_aggregationType columnDef.aggregationType}
+     * to specify the type of built-in aggregation the column should use.
+     *
+     * Available options are:
+     * - `uiGridConstants.aggregationTypes.sum` - add the values in this column to produce the aggregated value
+     * - `uiGridConstants.aggregationTypes.count` - count the number of rows to produce the aggregated value
+     * - `uiGridConstants.aggregationTypes.avg` - average the values in this column to produce the aggregated value
+     * - `uiGridConstants.aggregationTypes.min` - use the minimum value in this column as the aggregated value
+     * - `uiGridConstants.aggregationTypes.max` - use the maximum value in this column as the aggregated value
+     */
+    aggregationTypes: {
+      sum: 2,
+      count: 4,
+      avg: 8,
+      min: 16,
+      max: 32
+    },
+
+    // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them?
+    CURRENCY_SYMBOLS: ['\u0192', '$', '�', '$', '�', '�', '\u17db', '\u20a9', '\u20b1', '\u0e3f', '\u20ab'],
+
+    /**
+     * @ngdoc object
+     * @name scrollDirection
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Set on {@link ui.grid.class:Grid#properties_scrollDirection Grid.scrollDirection},
+     * to indicate the direction the grid is currently scrolling in
+     *
+     * Available options are:
+     * - `uiGridConstants.scrollDirection.UP` - set when the grid is scrolling up
+     * - `uiGridConstants.scrollDirection.DOWN` - set when the grid is scrolling down
+     * - `uiGridConstants.scrollDirection.LEFT` - set when the grid is scrolling left
+     * - `uiGridConstants.scrollDirection.RIGHT` - set when the grid is scrolling right
+     * - `uiGridConstants.scrollDirection.NONE` - set when the grid is not scrolling, this is the default
+     */
+    scrollDirection: {
+      UP: 'up',
+      DOWN: 'down',
+      LEFT: 'left',
+      RIGHT: 'right',
+      NONE: 'none'
+
+    },
+
+    /**
+     * @ngdoc object
+     * @name dataChange
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used with {@link ui.grid.core.api:PublicApi#methods_notifyDataChange PublicApi.notifyDataChange},
+     * {@link ui.grid.class:Grid#methods_callDataChangeCallbacks Grid.callDataChangeCallbacks},
+     * and {@link ui.grid.class:Grid#methods_registerDataChangeCallback Grid.registerDataChangeCallback}
+     * to specify the type of the event(s).
+     *
+     * Available options are:
+     * - `uiGridConstants.dataChange.ALL` - listeners fired on any of these events, fires listeners on all events.
+     * - `uiGridConstants.dataChange.EDIT` - fired when the data in a cell is edited
+     * - `uiGridConstants.dataChange.ROW` - fired when a row is added or removed
+     * - `uiGridConstants.dataChange.COLUMN` - fired when the column definitions are modified
+     * - `uiGridConstants.dataChange.OPTIONS` - fired when the grid options are modified
+     */
+    dataChange: {
+      ALL: 'all',
+      EDIT: 'edit',
+      ROW: 'row',
+      COLUMN: 'column',
+      OPTIONS: 'options'
+    },
+
+    /**
+     * @ngdoc object
+     * @name scrollbars
+     * @propertyOf ui.grid.service:uiGridConstants
+     * @description Used with {@link ui.grid.class:GridOptions#properties_enableHorizontalScrollbar GridOptions.enableHorizontalScrollbar}
+     * and {@link ui.grid.class:GridOptions#properties_enableVerticalScrollbar GridOptions.enableVerticalScrollbar}
+     * to specify the scrollbar policy for that direction.
+     *
+     * Available options are:
+     * - `uiGridConstants.scrollbars.NEVER` - never show scrollbars in this direction
+     * - `uiGridConstants.scrollbars.ALWAYS` - always show scrollbars in this direction
+     */
+
+    scrollbars: {
+      NEVER: 0,
+      ALWAYS: 1
+      //WHEN_NEEDED: 2
+    }
+  });
+
+})();
+
+angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUtil', 'uiGridConstants', function ($compile, $parse, gridUtil, uiGridConstants) {
+  var uiGridCell = {
+    priority: 0,
+    scope: false,
+    require: '?^uiGrid',
+    compile: function() {
+      return {
+        pre: function($scope, $elm, $attrs, uiGridCtrl) {
+          function compileTemplate() {
+            var compiledElementFn = $scope.col.compiledElementFn;
+
+            compiledElementFn($scope, function(clonedElement, scope) {
+              $elm.append(clonedElement);
+            });
+          }
+
+          // If the grid controller is present, use it to get the compiled cell template function
+          if (uiGridCtrl && $scope.col.compiledElementFn) {
+             compileTemplate();
+          }
+          // No controller, compile the element manually (for unit tests)
+          else {
+            if ( uiGridCtrl && !$scope.col.compiledElementFn ){
+              // gridUtil.logError('Render has been called before precompile.  Please log a ui-grid issue');  
+
+              $scope.col.getCompiledElementFn()
+                .then(function (compiledElementFn) {
+                  compiledElementFn($scope, function(clonedElement, scope) {
+                    $elm.append(clonedElement);
+                  });
+                });
+            }
+            else {
+              var html = $scope.col.cellTemplate
+                .replace(uiGridConstants.MODEL_COL_FIELD, 'row.entity.' + gridUtil.preEval($scope.col.field))
+                .replace(uiGridConstants.COL_FIELD, 'grid.getCellValue(row, col)');
+
+              var cellElement = $compile(html)($scope);
+              $elm.append(cellElement);
+            }
+          }
+        },
+        post: function($scope, $elm, $attrs, uiGridCtrl) {
+          var initColClass = $scope.col.getColClass(false);
+          $elm.addClass(initColClass);
+
+          var classAdded;
+          var updateClass = function( grid ){
+            var contents = $elm;
+            if ( classAdded ){
+              contents.removeClass( classAdded );
+              classAdded = null;
+            }
+
+            if (angular.isFunction($scope.col.cellClass)) {
+              classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
+            }
+            else {
+              classAdded = $scope.col.cellClass;
+            }
+            contents.addClass(classAdded);
+          };
+
+          if ($scope.col.cellClass) {
+            updateClass();
+          }
+          
+          // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
+          var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]);
+          
+          // watch the col and row to see if they change - which would indicate that we've scrolled or sorted or otherwise
+          // changed the row/col that this cell relates to, and we need to re-evaluate cell classes and maybe other things
+          var cellChangeFunction = function( n, o ){
+            if ( n !== o ) {
+              if ( classAdded || $scope.col.cellClass ){
+                updateClass();
+              }
+
+              // See if the column's internal class has changed
+              var newColClass = $scope.col.getColClass(false);
+              if (newColClass !== initColClass) {
+                $elm.removeClass(initColClass);
+                $elm.addClass(newColClass);
+                initColClass = newColClass;
+              }
+            }
+          };
+
+          // TODO(c0bra): Turn this into a deep array watch
+/*        shouldn't be needed any more given track by col.name
+          var colWatchDereg = $scope.$watch( 'col', cellChangeFunction );
+*/
+          var rowWatchDereg = $scope.$watch( 'row', cellChangeFunction );
+          
+          
+          var deregisterFunction = function() {
+            dataChangeDereg();
+//            colWatchDereg();
+            rowWatchDereg(); 
+          };
+          
+          $scope.$on( '$destroy', deregisterFunction );
+          $elm.on( '$destroy', deregisterFunction );
+        }
+      };
+    }
+  };
+
+  return uiGridCell;
+}]);
+
+
+(function(){
+
+angular.module('ui.grid')
+.service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil',
+function ( i18nService, uiGridConstants, gridUtil ) {
+/**
+ *  @ngdoc service
+ *  @name ui.grid.service:uiGridColumnMenuService
+ *
+ *  @description Services for working with column menus, factored out
+ *  to make the code easier to understand
+ */
+
+  var service = {
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name initialize
+     * @description  Sets defaults, puts a reference to the $scope on
+     * the uiGridController
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {controller} uiGridCtrl the uiGridController for the grid
+     * we're on
+     *
+     */
+    initialize: function( $scope, uiGridCtrl ){
+      $scope.grid = uiGridCtrl.grid;
+
+      // Store a reference to this link/controller in the main uiGrid controller
+      // to allow showMenu later
+      uiGridCtrl.columnMenuScope = $scope;
+
+      // Save whether we're shown or not so the columns can check
+      $scope.menuShown = false;
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name setColMenuItemWatch
+     * @description  Setup a watch on $scope.col.menuItems, and update
+     * menuItems based on this.  $scope.col needs to be set by the column
+     * before calling the menu.
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {controller} uiGridCtrl the uiGridController for the grid
+     * we're on
+     *
+     */
+    setColMenuItemWatch: function ( $scope ){
+      var deregFunction = $scope.$watch('col.menuItems', function (n) {
+        if (typeof(n) !== 'undefined' && n && angular.isArray(n)) {
+          n.forEach(function (item) {
+            if (typeof(item.context) === 'undefined' || !item.context) {
+              item.context = {};
+            }
+            item.context.col = $scope.col;
+          });
+
+          $scope.menuItems = $scope.defaultMenuItems.concat(n);
+        }
+        else {
+          $scope.menuItems = $scope.defaultMenuItems;
+        }
+      });
+
+      $scope.$on( '$destroy', deregFunction );
+    },
+
+
+    /**
+     * @ngdoc boolean
+     * @name enableSorting
+     * @propertyOf ui.grid.class:GridOptions.columnDef
+     * @description (optional) True by default. When enabled, this setting adds sort
+     * widgets to the column header, allowing sorting of the data in the individual column.
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name sortable
+     * @description  determines whether this column is sortable
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    sortable: function( $scope ) {
+      if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) {
+        return true;
+      }
+      else {
+        return false;
+      }
+    },
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name isActiveSort
+     * @description  determines whether the requested sort direction is current active, to
+     * allow highlighting in the menu
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {string} direction the direction that we'd have selected for us to be active
+     *
+     */
+    isActiveSort: function( $scope, direction ){
+      return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' &&
+              typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction);
+
+    },
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name suppressRemoveSort
+     * @description  determines whether we should suppress the removeSort option
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    suppressRemoveSort: function( $scope ) {
+      if ($scope.col && $scope.col.suppressRemoveSort) {
+        return true;
+      }
+      else {
+        return false;
+      }
+    },
+
+
+    /**
+     * @ngdoc boolean
+     * @name enableHiding
+     * @propertyOf ui.grid.class:GridOptions.columnDef
+     * @description (optional) True by default. When set to false, this setting prevents a user from hiding the column
+     * using the column menu or the grid menu.
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name hideable
+     * @description  determines whether a column can be hidden, by checking the enableHiding columnDef option
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    hideable: function( $scope ) {
+      if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) {
+        return false;
+      }
+      else {
+        return true;
+      }
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name getDefaultMenuItems
+     * @description  returns the default menu items for a column menu
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     *
+     */
+    getDefaultMenuItems: function( $scope ){
+      return [
+        {
+          title: i18nService.getSafeText('sort.ascending'),
+          icon: 'ui-grid-icon-sort-alt-up',
+          action: function($event) {
+            $event.stopPropagation();
+            $scope.sortColumn($event, uiGridConstants.ASC);
+          },
+          shown: function () {
+            return service.sortable( $scope );
+          },
+          active: function() {
+            return service.isActiveSort( $scope, uiGridConstants.ASC);
+          }
+        },
+        {
+          title: i18nService.getSafeText('sort.descending'),
+          icon: 'ui-grid-icon-sort-alt-down',
+          action: function($event) {
+            $event.stopPropagation();
+            $scope.sortColumn($event, uiGridConstants.DESC);
+          },
+          shown: function() {
+            return service.sortable( $scope );
+          },
+          active: function() {
+            return service.isActiveSort( $scope, uiGridConstants.DESC);
+          }
+        },
+        {
+          title: i18nService.getSafeText('sort.remove'),
+          icon: 'ui-grid-icon-cancel',
+          action: function ($event) {
+            $event.stopPropagation();
+            $scope.unsortColumn();
+          },
+          shown: function() {
+            return service.sortable( $scope ) &&
+                   typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' &&
+                   typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null &&
+                  !service.suppressRemoveSort( $scope );
+          }
+        },
+        {
+          title: i18nService.getSafeText('column.hide'),
+          icon: 'ui-grid-icon-cancel',
+          shown: function() {
+            return service.hideable( $scope );
+          },
+          action: function ($event) {
+            $event.stopPropagation();
+            $scope.hideColumn();
+          }
+        }
+      ];
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name getColumnElementPosition
+     * @description  gets the position information needed to place the column
+     * menu below the column header
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {GridCol} column the column we want to position below
+     * @param {element} $columnElement the column element we want to position below
+     * @returns {hash} containing left, top, offset, height, width
+     *
+     */
+    getColumnElementPosition: function( $scope, column, $columnElement ){
+      var positionData = {};
+      positionData.left = $columnElement[0].offsetLeft;
+      positionData.top = $columnElement[0].offsetTop;
+      positionData.parentLeft = $columnElement[0].offsetParent.offsetLeft;
+
+      // Get the grid scrollLeft
+      positionData.offset = 0;
+      if (column.grid.options.offsetLeft) {
+        positionData.offset = column.grid.options.offsetLeft;
+      }
+
+      positionData.height = gridUtil.elementHeight($columnElement, true);
+      positionData.width = gridUtil.elementWidth($columnElement, true);
+
+      return positionData;
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.service:uiGridColumnMenuService
+     * @name repositionMenu
+     * @description  Reposition the menu below the new column.  If the menu has no child nodes
+     * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again
+     * later to fix it
+     * @param {$scope} $scope the $scope from the uiGridColumnMenu
+     * @param {GridCol} column the column we want to position below
+     * @param {hash} positionData a hash containing left, top, offset, height, width
+     * @param {element} $elm the column menu element that we want to reposition
+     * @param {element} $columnElement the column element that we want to reposition underneath
+     *
+     */
+    repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) {
+      var menu = $elm[0].querySelectorAll('.ui-grid-menu');
+
+      // It's possible that the render container of the column we're attaching to is
+      // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft
+      // between the render container and the grid
+      var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container');
+      var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left;
+
+      var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft;
+
+      // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170
+      var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170);
+      var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10);
+
+      if ( menu.length !== 0 ){
+        var mid = menu[0].querySelectorAll('.ui-grid-menu-mid');
+        if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) {
+          myWidth = gridUtil.elementWidth(menu, true);
+          $scope.lastMenuWidth = myWidth;
+          column.lastMenuWidth = myWidth;
+
+          // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side
+          // Get the column menu right padding
+          paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10);
+          $scope.lastMenuPaddingRight = paddingRight;
+          column.lastMenuPaddingRight = paddingRight;
+        }
+      }
+
+      var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight;
+      if (left < positionData.offset){
+        left = positionData.offset;
+      }
+
+      $elm.css('left', left + 'px');
+      $elm.css('top', (positionData.top + positionData.height) + 'px');
+    }
+
+  };
+
+  return service;
+}])
+
+
+.directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document',
+function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) {
+/**
+ * @ngdoc directive
+ * @name ui.grid.directive:uiGridColumnMenu
+ * @description  Provides the column menu framework, leverages uiGridMenu underneath
+ *
+ */
+
+  var uiGridColumnMenu = {
+    priority: 0,
+    scope: true,
+    require: '^uiGrid',
+    templateUrl: 'ui-grid/uiGridColumnMenu',
+    replace: true,
+    link: function ($scope, $elm, $attrs, uiGridCtrl) {
+      uiGridColumnMenuService.initialize( $scope, uiGridCtrl );
+
+      $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope );
+
+      // Set the menu items for use with the column menu. The user can later add additional items via the watch
+      $scope.menuItems = $scope.defaultMenuItems;
+      uiGridColumnMenuService.setColMenuItemWatch( $scope );
+
+
+      /**
+       * @ngdoc method
+       * @methodOf ui.grid.directive:uiGridColumnMenu
+       * @name showMenu
+       * @description Shows the column menu.  If the menu is already displayed it
+       * calls the menu to ask it to hide (it will animate), then it repositions the menu
+       * to the right place whilst hidden (it will make an assumption on menu width),
+       * then it asks the menu to show (it will animate), then it repositions the menu again
+       * once we can calculate it's size.
+       * @param {GridCol} column the column we want to position below
+       * @param {element} $columnElement the column element we want to position below
+       */
+      $scope.showMenu = function(column, $columnElement, event) {
+        // Swap to this column
+        $scope.col = column;
+
+        // Get the position information for the column element
+        var colElementPosition = uiGridColumnMenuService.getColumnElementPosition( $scope, column, $columnElement );
+
+        if ($scope.menuShown) {
+          // we want to hide, then reposition, then show, but we want to wait for animations
+          // we set a variable, and then rely on the menu-hidden event to call the reposition and show
+          $scope.colElement = $columnElement;
+          $scope.colElementPosition = colElementPosition;
+          $scope.hideThenShow = true;
+
+          $scope.$broadcast('hide-menu', { originalEvent: event });
+        } else {
+          $scope.menuShown = true;
+          uiGridColumnMenuService.repositionMenu( $scope, column, colElementPosition, $elm, $columnElement );
+
+          $scope.colElement = $columnElement;
+          $scope.colElementPosition = colElementPosition;
+          $scope.$broadcast('show-menu', { originalEvent: event });
+
+        }
+      };
+
+
+      /**
+       * @ngdoc method
+       * @methodOf ui.grid.directive:uiGridColumnMenu
+       * @name hideMenu
+       * @description Hides the column menu.
+       * @param {boolean} broadcastTrigger true if we were triggered by a broadcast
+       * from the menu itself - in which case don't broadcast again as we'll get
+       * an infinite loop
+       */
+      $scope.hideMenu = function( broadcastTrigger ) {
+        $scope.menuShown = false;
+        if ( !broadcastTrigger ){
+          $scope.$broadcast('hide-menu');
+        }
+      };
+
+
+      $scope.$on('menu-hidden', function() {
+        if ( $scope.hideThenShow ){
+          delete $scope.hideThenShow;
+
+          uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
+          $scope.$broadcast('show-menu');
+
+          $scope.menuShown = true;
+        } else {
+          $scope.hideMenu( true );
+
+          if ($scope.col) {
+            //Focus on the menu button
+            gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
+          }
+        }
+      });
+
+      $scope.$on('menu-shown', function() {
+        $timeout( function() {
+          uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement );
+          //Focus on the first item
+          gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item', true);
+          delete $scope.colElementPosition;
+          delete $scope.columnElement;
+        }, 200);
+      });
+
+
+      /* Column methods */
+      $scope.sortColumn = function (event, dir) {
+        event.stopPropagation();
+
+        $scope.grid.sortColumn($scope.col, dir, true)
+          .then(function () {
+            $scope.grid.refresh();
+            $scope.hideMenu();
+          });
+      };
+
+      $scope.unsortColumn = function () {
+        $scope.col.unsort();
+
+        $scope.grid.refresh();
+        $scope.hideMenu();
+      };
+
+      //Since we are hiding this column the default hide action will fail so we need to focus somewhere else.
+      var setFocusOnHideColumn = function(){
+        $timeout(function(){
+          // Get the UID of the first
+          var focusToGridMenu = function(){
+            return gridUtil.focus.byId('grid-menu', $scope.grid);
+          };
+
+          var thisIndex;
+          $scope.grid.columns.some(function(element, index){
+            if (angular.equals(element, $scope.col)) {
+              thisIndex = index;
+              return true;
+            }
+          });
+
+          var previousVisibleCol;
+          // Try and find the next lower or nearest column to focus on
+          $scope.grid.columns.some(function(element, index){
+            if (!element.visible){
+              return false;
+            } // This columns index is below the current column index
+            else if ( index < thisIndex){
+              previousVisibleCol = element;
+            } // This elements index is above this column index and we haven't found one that is lower
+            else if ( index > thisIndex && !previousVisibleCol) {
+              // This is the next best thing
+              previousVisibleCol = element;
+              // We've found one so use it.
+              return true;
+            } // We've reached an element with an index above this column and the previousVisibleCol variable has been set
+            else if (index > thisIndex && previousVisibleCol) {
+              // We are done.
+              return true;
+            }
+          });
+          // If found then focus on it
+          if (previousVisibleCol){
+            var colClass = previousVisibleCol.getColClass();
+            gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){
+              if (reason !== 'canceled'){ // If this is canceled then don't perform the action
+                //The fallback action is to focus on the grid menu
+                return focusToGridMenu();
+              }
+            });
+          } else {
+            // Fallback action to focus on the grid menu
+            focusToGridMenu();
+          }
+        });
+      };
+
+      $scope.hideColumn = function () {
+        $scope.col.colDef.visible = false;
+        $scope.col.visible = false;
+
+        $scope.grid.queueGridRefresh();
+        $scope.hideMenu();
+        $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+        $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col );
+
+        // We are hiding so the default action of focusing on the button that opened this menu will fail.
+        setFocusOnHideColumn();
+      };
+    },
+
+
+
+    controller: ['$scope', function ($scope) {
+      var self = this;
+
+      $scope.$watch('menuItems', function (n) {
+        self.menuItems = n;
+      });
+    }]
+  };
+
+  return uiGridColumnMenu;
+
+}]);
+
+})();
+
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridFilter', ['$compile', '$templateCache', 'i18nService', 'gridUtil', function ($compile, $templateCache, i18nService, gridUtil) {
+
+    return {
+      compile: function() {
+        return {
+          pre: function ($scope, $elm, $attrs, controllers) {
+            $scope.col.updateFilters = function( filterable ){
+              $elm.children().remove();
+              if ( filterable ){
+                var template = $scope.col.filterHeaderTemplate;
+
+                $elm.append($compile(template)($scope));
+              }
+            };
+
+            $scope.$on( '$destroy', function() {
+              delete $scope.col.updateFilters;
+            });
+          },
+          post: function ($scope, $elm, $attrs, controllers){
+            $scope.aria = i18nService.getSafeText('headerCell.aria');
+            $scope.removeFilter = function(colFilter, index){
+              colFilter.term = null;
+              //Set the focus to the filter input after the action disables the button
+              gridUtil.focus.bySelector($elm, '.ui-grid-filter-input-' + index);
+            };
+          }
+        };
+      }
+    };
+  }]);
+})();
+
+(function () {
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridFooterCell', ['$timeout', 'gridUtil', 'uiGridConstants', '$compile',
+  function ($timeout, gridUtil, uiGridConstants, $compile) {
+    var uiGridFooterCell = {
+      priority: 0,
+      scope: {
+        col: '=',
+        row: '=',
+        renderIndex: '='
+      },
+      replace: true,
+      require: '^uiGrid',
+      compile: function compile(tElement, tAttrs, transclude) {
+        return {
+          pre: function ($scope, $elm, $attrs, uiGridCtrl) {
+            var cellFooter = $compile($scope.col.footerCellTemplate)($scope);
+            $elm.append(cellFooter);
+          },
+          post: function ($scope, $elm, $attrs, uiGridCtrl) {
+            //$elm.addClass($scope.col.getColClass(false));
+            $scope.grid = uiGridCtrl.grid;
+
+            var initColClass = $scope.col.getColClass(false);
+            $elm.addClass(initColClass);
+
+            // apply any footerCellClass
+            var classAdded;
+            var updateClass = function( grid ){
+              var contents = $elm;
+              if ( classAdded ){
+                contents.removeClass( classAdded );
+                classAdded = null;
+              }
+  
+              if (angular.isFunction($scope.col.footerCellClass)) {
+                classAdded = $scope.col.footerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
+              }
+              else {
+                classAdded = $scope.col.footerCellClass;
+              }
+              contents.addClass(classAdded);
+            };
+  
+            if ($scope.col.footerCellClass) {
+              updateClass();
+            }
+
+            $scope.col.updateAggregationValue();
+
+            // Watch for column changes so we can alter the col cell class properly
+/* shouldn't be needed any more, given track by col.name
+            $scope.$watch('col', function (n, o) {
+              if (n !== o) {
+                // See if the column's internal class has changed
+                var newColClass = $scope.col.getColClass(false);
+                if (newColClass !== initColClass) {
+                  $elm.removeClass(initColClass);
+                  $elm.addClass(newColClass);
+                  initColClass = newColClass;
+                }
+              }
+            });
+*/
+
+
+            // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
+            var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]);
+            // listen for visible rows change and update aggregation values
+            $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue );
+            $scope.grid.api.core.on.rowsRendered( $scope, updateClass );
+            $scope.$on( '$destroy', dataChangeDereg );
+          }
+        };
+      }
+    };
+
+    return uiGridFooterCell;
+  }]);
+
+})();
+
+(function () {
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
+
+    return {
+      restrict: 'EA',
+      replace: true,
+      // priority: 1000,
+      require: ['^uiGrid', '^uiGridRenderContainer'],
+      scope: true,
+      compile: function ($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            $scope.grid = uiGridCtrl.grid;
+            $scope.colContainer = containerCtrl.colContainer;
+
+            containerCtrl.footer = $elm;
+
+            var footerTemplate = $scope.grid.options.footerTemplate;
+            gridUtil.getTemplate(footerTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+
+                var newElm = $compile(template)($scope);
+                $elm.append(newElm);
+
+                if (containerCtrl) {
+                  // Inject a reference to the footer viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
+                  var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
+
+                  if (footerViewport) {
+                    containerCtrl.footerViewport = footerViewport;
+                  }
+                }
+              });
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            // gridUtil.logDebug('ui-grid-footer link');
+
+            var grid = uiGridCtrl.grid;
+
+            // Don't animate footer cells
+            gridUtil.disableAnimations($elm);
+
+            containerCtrl.footer = $elm;
+
+            var footerViewport = $elm[0].getElementsByClassName('ui-grid-footer-viewport')[0];
+            if (footerViewport) {
+              containerCtrl.footerViewport = footerViewport;
+            }
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+(function () {
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridGridFooter', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', function ($templateCache, $compile, uiGridConstants, gridUtil, $timeout) {
+
+    return {
+      restrict: 'EA',
+      replace: true,
+      // priority: 1000,
+      require: '^uiGrid',
+      scope: true,
+      compile: function ($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, uiGridCtrl) {
+
+            $scope.grid = uiGridCtrl.grid;
+
+
+
+            var footerTemplate = $scope.grid.options.gridFooterTemplate;
+            gridUtil.getTemplate(footerTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+
+                var newElm = $compile(template)($scope);
+                $elm.append(newElm);
+              });
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridGroupPanel', ["$compile", "uiGridConstants", "gridUtil", function($compile, uiGridConstants, gridUtil) {
+    var defaultTemplate = 'ui-grid/ui-grid-group-panel';
+
+    return {
+      restrict: 'EA',
+      replace: true,
+      require: '?^uiGrid',
+      scope: false,
+      compile: function($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, uiGridCtrl) {
+            var groupPanelTemplate = $scope.grid.options.groupPanelTemplate  || defaultTemplate;
+
+             gridUtil.getTemplate(groupPanelTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+                
+                var newElm = $compile(template)($scope);
+                $elm.append(newElm);
+              });
+          },
+
+          post: function ($scope, $elm, $attrs, uiGridCtrl) {
+            $elm.bind('$destroy', function() {
+              // scrollUnbinder();
+            });
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridHeaderCell', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'ScrollEvent', 'i18nService',
+  function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, ScrollEvent, i18nService) {
+    // Do stuff after mouse has been down this many ms on the header cell
+    var mousedownTimeout = 500;
+    var changeModeTimeout = 500;    // length of time between a touch event and a mouse event being recognised again, and vice versa
+
+    var uiGridHeaderCell = {
+      priority: 0,
+      scope: {
+        col: '=',
+        row: '=',
+        renderIndex: '='
+      },
+      require: ['^uiGrid', '^uiGridRenderContainer'],
+      replace: true,
+      compile: function() {
+        return {
+          pre: function ($scope, $elm, $attrs) {
+            var cellHeader = $compile($scope.col.headerCellTemplate)($scope);
+            $elm.append(cellHeader);
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var renderContainerCtrl = controllers[1];
+
+            $scope.i18n = {
+              headerCell: i18nService.getSafeText('headerCell'),
+              sort: i18nService.getSafeText('sort')
+            };
+            $scope.isSortPriorityVisible = function() {
+              //show sort priority if column is sorted and there is at least one other sorted column
+              return angular.isNumber($scope.col.sort.priority) && $scope.grid.columns.some(function(element, index){
+                  return angular.isNumber(element.sort.priority) && element !== $scope.col;
+                });
+            };
+            $scope.getSortDirectionAriaLabel = function(){
+              var col = $scope.col;
+              //Trying to recreate this sort of thing but it was getting messy having it in the template.
+              //Sort direction {{col.sort.direction == asc ? 'ascending' : ( col.sort.direction == desc ? 'descending':'none')}}. {{col.sort.priority ? {{columnPriorityText}} {{col.sort.priority}} : ''}
+              var sortDirectionText = col.sort.direction === uiGridConstants.ASC ? $scope.i18n.sort.ascending : ( col.sort.direction === uiGridConstants.DESC ? $scope.i18n.sort.descending : $scope.i18n.sort.none);
+              var label = sortDirectionText;
+
+              if ($scope.isSortPriorityVisible()) {
+                label = label + '. ' + $scope.i18n.headerCell.priority + ' ' + col.sort.priority;
+              }
+              return label;
+            };
+
+            $scope.grid = uiGridCtrl.grid;
+
+            $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId];
+
+            var initColClass = $scope.col.getColClass(false);
+            $elm.addClass(initColClass);
+
+            // Hide the menu by default
+            $scope.menuShown = false;
+
+            // Put asc and desc sort directions in scope
+            $scope.asc = uiGridConstants.ASC;
+            $scope.desc = uiGridConstants.DESC;
+
+            // Store a reference to menu element
+            var $colMenu = angular.element( $elm[0].querySelectorAll('.ui-grid-header-cell-menu') );
+
+            var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') );
+
+
+            // apply any headerCellClass
+            var classAdded;
+            var previousMouseX;
+
+            // filter watchers
+            var filterDeregisters = [];
+
+
+            /*
+             * Our basic approach here for event handlers is that we listen for a down event (mousedown or touchstart).
+             * Once we have a down event, we need to work out whether we have a click, a drag, or a
+             * hold.  A click would sort the grid (if sortable).  A drag would be used by moveable, so
+             * we ignore it.  A hold would open the menu.
+             *
+             * So, on down event, we put in place handlers for move and up events, and a timer.  If the
+             * timer expires before we see a move or up, then we have a long press and hence a column menu open.
+             * If the up happens before the timer, then we have a click, and we sort if the column is sortable.
+             * If a move happens before the timer, then we are doing column move, so we do nothing, the moveable feature
+             * will handle it.
+             *
+             * To deal with touch enabled devices that also have mice, we only create our handlers when
+             * we get the down event, and we create the corresponding handlers - if we're touchstart then
+             * we get touchmove and touchend, if we're mousedown then we get mousemove and mouseup.
+             *
+             * We also suppress the click action whilst this is happening - otherwise after the mouseup there
+             * will be a click event and that can cause the column menu to close
+             *
+             */
+
+            $scope.downFn = function( event ){
+              event.stopPropagation();
+
+              if (typeof(event.originalEvent) !== 'undefined' && event.originalEvent !== undefined) {
+                event = event.originalEvent;
+              }
+
+              // Don't show the menu if it's not the left button
+              if (event.button && event.button !== 0) {
+                return;
+              }
+              previousMouseX = event.pageX;
+
+              $scope.mousedownStartTime = (new Date()).getTime();
+              $scope.mousedownTimeout = $timeout(function() { }, mousedownTimeout);
+
+              $scope.mousedownTimeout.then(function () {
+                if ( $scope.colMenu ) {
+                  uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm, event);
+                }
+              });
+
+              uiGridCtrl.fireEvent(uiGridConstants.events.COLUMN_HEADER_CLICK, {event: event, columnName: $scope.col.colDef.name});
+
+              $scope.offAllEvents();
+              if ( event.type === 'touchstart'){
+                $document.on('touchend', $scope.upFn);
+                $document.on('touchmove', $scope.moveFn);
+              } else if ( event.type === 'mousedown' ){
+                $document.on('mouseup', $scope.upFn);
+                $document.on('mousemove', $scope.moveFn);
+              }
+            };
+
+            $scope.upFn = function( event ){
+              event.stopPropagation();
+              $timeout.cancel($scope.mousedownTimeout);
+              $scope.offAllEvents();
+              $scope.onDownEvents(event.type);
+
+              var mousedownEndTime = (new Date()).getTime();
+              var mousedownTime = mousedownEndTime - $scope.mousedownStartTime;
+
+              if (mousedownTime > mousedownTimeout) {
+                // long click, handled above with mousedown
+              }
+              else {
+                // short click
+                if ( $scope.sortable ){
+                  $scope.handleClick(event);
+                }
+              }
+            };
+
+            $scope.moveFn = function( event ){
+              // Chrome is known to fire some bogus move events.
+              var changeValue = event.pageX - previousMouseX;
+              if ( changeValue === 0 ){ return; }
+
+              // we're a move, so do nothing and leave for column move (if enabled) to take over
+              $timeout.cancel($scope.mousedownTimeout);
+              $scope.offAllEvents();
+              $scope.onDownEvents(event.type);
+            };
+
+            $scope.clickFn = function ( event ){
+              event.stopPropagation();
+              $contentsElm.off('click', $scope.clickFn);
+            };
+
+
+            $scope.offAllEvents = function(){
+              $contentsElm.off('touchstart', $scope.downFn);
+              $contentsElm.off('mousedown', $scope.downFn);
+
+              $document.off('touchend', $scope.upFn);
+              $document.off('mouseup', $scope.upFn);
+
+              $document.off('touchmove', $scope.moveFn);
+              $document.off('mousemove', $scope.moveFn);
+
+              $contentsElm.off('click', $scope.clickFn);
+            };
+
+            $scope.onDownEvents = function( type ){
+              // If there is a previous event, then wait a while before
+              // activating the other mode - i.e. if the last event was a touch event then
+              // don't enable mouse events for a wee while (500ms or so)
+              // Avoids problems with devices that emulate mouse events when you have touch events
+
+              switch (type){
+                case 'touchmove':
+                case 'touchend':
+                  $contentsElm.on('click', $scope.clickFn);
+                  $contentsElm.on('touchstart', $scope.downFn);
+                  $timeout(function(){
+                    $contentsElm.on('mousedown', $scope.downFn);
+                  }, changeModeTimeout);
+                  break;
+                case 'mousemove':
+                case 'mouseup':
+                  $contentsElm.on('click', $scope.clickFn);
+                  $contentsElm.on('mousedown', $scope.downFn);
+                  $timeout(function(){
+                    $contentsElm.on('touchstart', $scope.downFn);
+                  }, changeModeTimeout);
+                  break;
+                default:
+                  $contentsElm.on('click', $scope.clickFn);
+                  $contentsElm.on('touchstart', $scope.downFn);
+                  $contentsElm.on('mousedown', $scope.downFn);
+              }
+            };
+
+
+            var updateHeaderOptions = function( grid ){
+              var contents = $elm;
+              if ( classAdded ){
+                contents.removeClass( classAdded );
+                classAdded = null;
+              }
+
+              if (angular.isFunction($scope.col.headerCellClass)) {
+                classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex);
+              }
+              else {
+                classAdded = $scope.col.headerCellClass;
+              }
+              contents.addClass(classAdded);
+
+              $timeout(function (){
+                var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body'];
+                $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] );
+              });
+
+              // Figure out whether this column is sortable or not
+              if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) {
+                $scope.sortable = true;
+              }
+              else {
+                $scope.sortable = false;
+              }
+
+              // Figure out whether this column is filterable or not
+              var oldFilterable = $scope.filterable;
+              if (uiGridCtrl.grid.options.enableFiltering && $scope.col.enableFiltering) {
+                $scope.filterable = true;
+              }
+              else {
+                $scope.filterable = false;
+              }
+
+              if ( oldFilterable !== $scope.filterable){
+                if ( typeof($scope.col.updateFilters) !== 'undefined' ){
+                  $scope.col.updateFilters($scope.filterable);
+                }
+
+                // if column is filterable add a filter watcher
+                if ($scope.filterable) {
+                  $scope.col.filters.forEach( function(filter, i) {
+                    filterDeregisters.push($scope.$watch('col.filters[' + i + '].term', function(n, o) {
+                      if (n !== o) {
+                        uiGridCtrl.grid.api.core.raise.filterChanged();
+                        uiGridCtrl.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+                        uiGridCtrl.grid.queueGridRefresh();
+                      }
+                    }));
+                  });
+                  $scope.$on('$destroy', function() {
+                    filterDeregisters.forEach( function(filterDeregister) {
+                      filterDeregister();
+                    });
+                  });
+                } else {
+                  filterDeregisters.forEach( function(filterDeregister) {
+                    filterDeregister();
+                  });
+                }
+
+              }
+
+              // figure out whether we support column menus
+              if ($scope.col.grid.options && $scope.col.grid.options.enableColumnMenus !== false &&
+                      $scope.col.colDef && $scope.col.colDef.enableColumnMenu !== false){
+                $scope.colMenu = true;
+              } else {
+                $scope.colMenu = false;
+              }
+
+              /**
+              * @ngdoc property
+              * @name enableColumnMenu
+              * @propertyOf ui.grid.class:GridOptions.columnDef
+              * @description if column menus are enabled, controls the column menus for this specific
+              * column (i.e. if gridOptions.enableColumnMenus, then you can control column menus
+              * using this option. If gridOptions.enableColumnMenus === false then you get no column
+              * menus irrespective of the value of this option ).  Defaults to true.
+              *
+              */
+              /**
+              * @ngdoc property
+              * @name enableColumnMenus
+              * @propertyOf ui.grid.class:GridOptions.columnDef
+              * @description Override for column menus everywhere - if set to false then you get no
+              * column menus.  Defaults to true.
+              *
+              */
+
+              $scope.offAllEvents();
+
+              if ($scope.sortable || $scope.colMenu) {
+                $scope.onDownEvents();
+
+                $scope.$on('$destroy', function () {
+                  $scope.offAllEvents();
+                });
+              }
+            };
+
+/*
+            $scope.$watch('col', function (n, o) {
+              if (n !== o) {
+                // See if the column's internal class has changed
+                var newColClass = $scope.col.getColClass(false);
+                if (newColClass !== initColClass) {
+                  $elm.removeClass(initColClass);
+                  $elm.addClass(newColClass);
+                  initColClass = newColClass;
+                }
+              }
+            });
+*/
+            updateHeaderOptions();
+
+            // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs
+            var dataChangeDereg = $scope.grid.registerDataChangeCallback( updateHeaderOptions, [uiGridConstants.dataChange.COLUMN]);
+
+            $scope.$on( '$destroy', dataChangeDereg );
+
+            $scope.handleClick = function(event) {
+              // If the shift key is being held down, add this column to the sort
+              var add = false;
+              if (event.shiftKey) {
+                add = true;
+              }
+
+              // Sort this column then rebuild the grid's rows
+              uiGridCtrl.grid.sortColumn($scope.col, add)
+                .then(function () {
+                  if (uiGridCtrl.columnMenuScope) { uiGridCtrl.columnMenuScope.hideMenu(); }
+                  uiGridCtrl.grid.refresh();
+                });
+            };
+
+
+            $scope.toggleMenu = function(event) {
+              event.stopPropagation();
+
+              // If the menu is already showing...
+              if (uiGridCtrl.columnMenuScope.menuShown) {
+                // ... and we're the column the menu is on...
+                if (uiGridCtrl.columnMenuScope.col === $scope.col) {
+                  // ... hide it
+                  uiGridCtrl.columnMenuScope.hideMenu();
+                }
+                // ... and we're NOT the column the menu is on
+                else {
+                  // ... move the menu to our column
+                  uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
+                }
+              }
+              // If the menu is NOT showing
+              else {
+                // ... show it on our column
+                uiGridCtrl.columnMenuScope.showMenu($scope.col, $elm);
+              }
+            };
+          }
+        };
+      }
+    };
+
+    return uiGridHeaderCell;
+  }]);
+
+})();
+
+(function(){
+  'use strict';
+
+  angular.module('ui.grid').directive('uiGridHeader', ['$templateCache', '$compile', 'uiGridConstants', 'gridUtil', '$timeout', 'ScrollEvent',
+    function($templateCache, $compile, uiGridConstants, gridUtil, $timeout, ScrollEvent) {
+    var defaultTemplate = 'ui-grid/ui-grid-header';
+    var emptyTemplate = 'ui-grid/ui-grid-no-header';
+
+    return {
+      restrict: 'EA',
+      // templateUrl: 'ui-grid/ui-grid-header',
+      replace: true,
+      // priority: 1000,
+      require: ['^uiGrid', '^uiGridRenderContainer'],
+      scope: true,
+      compile: function($elm, $attrs) {
+        return {
+          pre: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            $scope.grid = uiGridCtrl.grid;
+            $scope.colContainer = containerCtrl.colContainer;
+
+            updateHeaderReferences();
+            
+            var headerTemplate;
+            if (!$scope.grid.options.showHeader) {
+              headerTemplate = emptyTemplate;
+            }
+            else {
+              headerTemplate = ($scope.grid.options.headerTemplate) ? $scope.grid.options.headerTemplate : defaultTemplate;            
+            }
+
+            gridUtil.getTemplate(headerTemplate)
+              .then(function (contents) {
+                var template = angular.element(contents);
+                
+                var newElm = $compile(template)($scope);
+                $elm.replaceWith(newElm);
+
+                // And update $elm to be the new element
+                $elm = newElm;
+
+                updateHeaderReferences();
+
+                if (containerCtrl) {
+                  // Inject a reference to the header viewport (if it exists) into the grid controller for use in the horizontal scroll handler below
+                  var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
+
+
+                  if (headerViewport) {
+                    containerCtrl.headerViewport = headerViewport;
+                    angular.element(headerViewport).on('scroll', scrollHandler);
+                    $scope.$on('$destroy', function () {
+                      angular.element(headerViewport).off('scroll', scrollHandler);
+                    });
+                  }
+                }
+
+                $scope.grid.queueRefresh();
+              });
+
+            function updateHeaderReferences() {
+              containerCtrl.header = containerCtrl.colContainer.header = $elm;
+
+              var headerCanvases = $elm[0].getElementsByClassName('ui-grid-header-canvas');
+
+              if (headerCanvases.length > 0) {
+                containerCtrl.headerCanvas = containerCtrl.colContainer.headerCanvas = headerCanvases[0];
+              }
+              else {
+                containerCtrl.headerCanvas = null;
+              }
+            }
+
+            function scrollHandler(evt) {
+              if (uiGridCtrl.grid.isScrollingHorizontally) {
+                return;
+              }
+              var newScrollLeft = gridUtil.normalizeScrollLeft(containerCtrl.headerViewport, uiGridCtrl.grid);
+              var horizScrollPercentage = containerCtrl.colContainer.scrollHorizontal(newScrollLeft);
+
+              var scrollEvent = new ScrollEvent(uiGridCtrl.grid, null, containerCtrl.colContainer, ScrollEvent.Sources.ViewPortScroll);
+              scrollEvent.newScrollLeft = newScrollLeft;
+              if ( horizScrollPercentage > -1 ){
+                scrollEvent.x = { percentage: horizScrollPercentage };
+              }
+
+              uiGridCtrl.grid.scrollContainers(null, scrollEvent);
+            }
+          },
+
+          post: function ($scope, $elm, $attrs, controllers) {
+            var uiGridCtrl = controllers[0];
+            var containerCtrl = controllers[1];
+
+            // gridUtil.logDebug('ui-grid-header link');
+
+            var grid = uiGridCtrl.grid;
+
+            // Don't animate header cells
+            gridUtil.disableAnimations($elm);
+
+            function updateColumnWidths() {
+              // this styleBuilder always runs after the renderContainer, so we can rely on the column widths
+              // already being populated correctly
+
+              var columnCache = containerCtrl.colContainer.visibleColumnCache;
+              
+              // Build the CSS
+              // uiGridCtrl.grid.columns.forEach(function (column) {
+              var ret = '';
+              var canvasWidth = 0;
+              columnCache.forEach(function (column) {
+                ret = ret + column.getColClassDefinition();
+                canvasWidth += column.drawnWidth;
+              });
+
+              containerCtrl.colContainer.canvasWidth = canvasWidth;
+              
+              // Return the styles back to buildStyles which pops them into the `customStyles` scope variable
+              return ret;
+            }
+            
+            containerCtrl.header = $elm;
+            
+            var headerViewport = $elm[0].getElementsByClassName('ui-grid-header-viewport')[0];
+            if (headerViewport) {
+              containerCtrl.headerViewport = headerViewport;
+            }
+
+            //todo: remove this if by injecting gridCtrl into unit tests
+            if (uiGridCtrl) {
+              uiGridCtrl.grid.registerStyleComputation({
+                priority: 15,
+                func: updateColumnWidths
+              });
+            }
+          }
+        };
+      }
+    };
+  }]);
+
+})();
+
+(function(){
+
+angular.module('ui.grid')
+.service('uiGridGridMenuService', [ 'gridUtil', 'i18nService', 'uiGridConstants', function( gridUtil, i18nService, uiGridConstants ) {
+  /**
+   *  @ngdoc service
+   *  @name ui.grid.gridMenuService
+   *
+   *  @description Methods for working with the grid menu
+   */
+
+  var service = {
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name initialize
+     * @description Sets up the gridMenu. Most importantly, sets our
+     * scope onto the grid object as grid.gridMenuScope, allowing us
+     * to operate when passed only the grid.  Second most importantly,
+     * we register the 'addToGridMenu' and 'removeFromGridMenu' methods
+     * on the core api.
+     * @param {$scope} $scope the scope of this gridMenu
+     * @param {Grid} grid the grid to which this gridMenu is associated
+     */
+    initialize: function( $scope, grid ){
+      grid.gridMenuScope = $scope;
+      $scope.grid = grid;
+      $scope.registeredMenuItems = [];
+
+      // not certain this is needed, but would be bad to create a memory leak
+      $scope.$on('$destroy', function() {
+        if ( $scope.grid && $scope.grid.gridMenuScope ){
+          $scope.grid.gridMenuScope = null;
+        }
+        if ( $scope.grid ){
+          $scope.grid = null;
+        }
+        if ( $scope.registeredMenuItems ){
+          $scope.registeredMenuItems = null;
+        }
+      });
+
+      $scope.registeredMenuItems = [];
+
+      /**
+       * @ngdoc function
+       * @name addToGridMenu
+       * @methodOf ui.grid.core.api:PublicApi
+       * @description add items to the grid menu.  Used by features
+       * to add their menu items if they are enabled, can also be used by
+       * end users to add menu items.  This method has the advantage of allowing
+       * remove again, which can simplify management of which items are included
+       * in the menu when.  (Noting that in most cases the shown and active functions
+       * provide a better way to handle visibility of menu items)
+       * @param {Grid} grid the grid on which we are acting
+       * @param {array} items menu items in the format as described in the tutorial, with
+       * the added note that if you want to use remove you must also specify an `id` field,
+       * which is provided when you want to remove an item.  The id should be unique.
+       *
+       */
+      grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu );
+
+      /**
+       * @ngdoc function
+       * @name removeFromGridMenu
+       * @methodOf ui.grid.core.api:PublicApi
+       * @description Remove an item from the grid menu based on a provided id. Assumes
+       * that the id is unique, removes only the last instance of that id. Does nothing if
+       * the specified id is not found
+       * @param {Grid} grid the grid on which we are acting
+       * @param {string} id the id we'd like to remove from the menu
+       *
+       */
+      grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu );
+    },
+
+
+    /**
+     * @ngdoc function
+     * @name addToGridMenu
+     * @propertyOf ui.grid.gridMenuService
+     * @description add items to the grid menu.  Used by features
+     * to add their menu items if they are enabled, can also be used by
+     * end users to add menu items.  This method has the advantage of allowing
+     * remove again, which can simplify management of which items are included
+     * in the menu when.  (Noting that in most cases the shown and active functions
+     * provide a better way to handle visibility of menu items)
+     * @param {Grid} grid the grid on which we are acting
+     * @param {array} items menu items in the format as described in the tutorial, with
+     * the added note that if you want to use remove you must also specify an `id` field,
+     * which is provided when you want to remove an item.  The id should be unique.
+     *
+     */
+    addToGridMenu: function( grid, menuItems ) {
+      if ( !angular.isArray( menuItems ) ) {
+        gridUtil.logError( 'addToGridMenu: menuItems must be an array, and is not, not adding any items');
+      } else {
+        if ( grid.gridMenuScope ){
+          grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems ? grid.gridMenuScope.registeredMenuItems : [];
+          grid.gridMenuScope.registeredMenuItems = grid.gridMenuScope.registeredMenuItems.concat( menuItems );
+        } else {
+          gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present.  Timing issue?  Please log issue with ui-grid');
+        }
+      }
+    },
+
+
+    /**
+     * @ngdoc function
+     * @name removeFromGridMenu
+     * @methodOf ui.grid.gridMenuService
+     * @description Remove an item from the grid menu based on a provided id.  Assumes
+     * that the id is unique, removes only the last instance of that id.  Does nothing if
+     * the specified id is not found.  If there is no gridMenuScope or registeredMenuItems
+     * then do nothing silently - the desired result is those menu items not be present and they
+     * aren't.
+     * @param {Grid} grid the grid on which we are acting
+     * @param {string} id the id we'd like to remove from the menu
+     *
+     */
+    removeFromGridMenu: function( grid, id ){
+      var foundIndex = -1;
+
+      if ( grid && grid.gridMenuScope ){
+        grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) {
+          if ( value.id === id ){
+            if (foundIndex > -1) {
+              gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' );
+            } else {
+
+              foundIndex = index;
+            }
+          }
+        });
+      }
+
+      if ( foundIndex > -1 ){
+        grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 );
+      }
+    },
+
+
+    /**
+     * @ngdoc array
+     * @name gridMenuCustomItems
+     * @propertyOf ui.grid.class:GridOptions
+     * @description (optional) An array of menu items that should be added to
+     * the gridMenu.  Follow the format documented in the tutorial for column
+     * menu customisation.  The context provided to the action function will
+     * include context.grid.  An alternative if working with dynamic menus is to use the
+     * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles
+     * some of the management of items for you.
+     *
+     */
+    /**
+     * @ngdoc boolean
+     * @name gridMenuShowHideColumns
+     * @propertyOf ui.grid.class:GridOptions
+     * @description true by default, whether the grid menu should allow hide/show
+     * of columns
+     *
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name getMenuItems
+     * @description Decides the menu items to show in the menu.  This is a
+     * combination of:
+     *
+     * - the default menu items that are always included,
+     * - any menu items that have been provided through the addMenuItem api. These
+     *   are typically added by features within the grid
+     * - any menu items included in grid.options.gridMenuCustomItems.  These can be
+     *   changed dynamically, as they're always recalculated whenever we show the
+     *   menu
+     * @param {$scope} $scope the scope of this gridMenu, from which we can find all
+     * the information that we need
+     * @returns {array} an array of menu items that can be shown
+     */
+    getMenuItems: function( $scope ) {
+      var menuItems = [
+        // this is where we add any menu items we want to always include
+      ];
+
+      if ( $scope.grid.options.gridMenuCustomItems ){
+        if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){
+          gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not');
+        } else {
+          menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems );
+        }
+      }
+
+      var clearFilters = [{
+        title: i18nService.getSafeText('gridMenu.clearAllFilters'),
+        action: function ($event) {
+          $scope.grid.clearAllFilters(undefined, true, undefined);
+        },
+        shown: function() {
+          return $scope.grid.options.enableFiltering;
+        },
+        order: 100
+      }];
+      menuItems = menuItems.concat( clearFilters );
+
+      menuItems = menuItems.concat( $scope.registeredMenuItems );
+
+      if ( $scope.grid.options.gridMenuShowHideColumns !== false ){
+        menuItems = menuItems.concat( service.showHideColumns( $scope ) );
+      }
+
+      menuItems.sort(function(a, b){
+        return a.order - b.order;
+      });
+
+      return menuItems;
+    },
+
+
+    /**
+     * @ngdoc array
+     * @name gridMenuTitleFilter
+     * @propertyOf ui.grid.class:GridOptions
+     * @description (optional) A function that takes a title string
+     * (usually the col.displayName), and converts it into a display value.  The function
+     * must return either a string or a promise.
+     *
+     * Used for internationalization of the grid menu column names - for angular-translate
+     * you can pass $translate as the function, for i18nService you can pass getSafeText as the
+     * function
+     * @example
+     * <pre>
+     *   gridOptions = {
+     *     gridMenuTitleFilter: $translate
+     *   }
+     * </pre>
+     */
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name showHideColumns
+     * @description Adds two menu items for each of the columns in columnDefs.  One
+     * menu item for hide, one menu item for show.  Each is visible when appropriate
+     * (show when column is not visible, hide when column is visible).  Each toggles
+     * the visible property on the columnDef using toggleColumnVisibility
+     * @param {$scope} $scope of a gridMenu, which contains a reference to the grid
+     */
+    showHideColumns: function( $scope ){
+      var showHideColumns = [];
+      if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
+        return showHideColumns;
+      }
+
+      // add header for columns
+      showHideColumns.push({
+        title: i18nService.getSafeText('gridMenu.columns'),
+        order: 300
+      });
+
+      $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
+
+      $scope.grid.options.columnDefs.forEach( function( colDef, index ){
+        if ( colDef.enableHiding !== false ){
+          // add hide menu item - shows an OK icon as we only show when column is already visible
+          var menuItem = {
+            icon: 'ui-grid-icon-ok',
+            action: function($event) {
+              $event.stopPropagation();
+              service.toggleColumnVisibility( this.context.gridCol );
+            },
+            shown: function() {
+              return this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined;
+            },
+            context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
+            leaveOpen: true,
+            order: 301 + index * 2
+          };
+          service.setMenuItemTitle( menuItem, colDef, $scope.grid );
+          showHideColumns.push( menuItem );
+
+          // add show menu item - shows no icon as we only show when column is invisible
+          menuItem = {
+            icon: 'ui-grid-icon-cancel',
+            action: function($event) {
+              $event.stopPropagation();
+              service.toggleColumnVisibility( this.context.gridCol );
+            },
+            shown: function() {
+              return !(this.context.gridCol.colDef.visible === true || this.context.gridCol.colDef.visible === undefined);
+            },
+            context: { gridCol: $scope.grid.getColumn(colDef.name || colDef.field) },
+            leaveOpen: true,
+            order: 301 + index * 2 + 1
+          };
+          service.setMenuItemTitle( menuItem, colDef, $scope.grid );
+          showHideColumns.push( menuItem );
+        }
+      });
+      return showHideColumns;
+    },
+
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name setMenuItemTitle
+     * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
+     * item if it returns a string, otherwise waiting for the promise to resolve or reject then
+     * putting the result into the title
+     * @param {object} menuItem the menuItem we want to put the title on
+     * @param {object} colDef the colDef from which we can get displayName, name or field
+     * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
+     *
+     */
+    setMenuItemTitle: function( menuItem, colDef, grid ){
+      var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
+
+      if ( typeof(title) === 'string' ){
+        menuItem.title = title;
+      } else if ( title.then ){
+        // must be a promise
+        menuItem.title = "";
+        title.then( function( successValue ) {
+          menuItem.title = successValue;
+        }, function( errorValue ) {
+          menuItem.title = errorValue;
+        });
+      } else {
+        gridUtil.logError('Expected gridMenuTitleFilter to return a string or a promise, it has returned neither, bad config');
+        menuItem.title = 'badconfig';
+      }
+    },
+
+    /**
+     * @ngdoc method
+     * @methodOf ui.grid.gridMenuService
+     * @name toggleColumnVisibility
+     * @description Toggles the visibility of an individual column.  Expects to be
+     * provided a context that has on it a gridColumn, which is the column that
+     * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
+     * @param {GridCol} gridCol the column that we want to toggle
+     *
+     */
+    toggleColumnVisibility: function( gridCol ) {
+      gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
+
+      gridCol.grid.refresh();
+      gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
+      gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
+    }
+  };
+
+  return service;
+}])
+
+
+
+.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
+function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
+
+  return {
+    priority: 0,
+    scope: true,
+    require: ['^uiGrid'],
+    templateUrl: 'ui-grid/ui-grid-menu-button',
+    replace: true,
+
+    link: function ($scope, $elm, $attrs, controllers) {
+      var uiGridCtrl = controllers[0];
+
+      // For the aria label
+      $scope.i18n = {
+        aria: i18nService.getSafeText('gridMenu.aria')
+      };
+
+      uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
+
+      $scope.shown = false;
+
+      $scope.toggleMenu = function () {
+        if ( $scope.shown ){
+          $scope.$broadcast('hide-menu');
+          $scope.shown = false;
+        } else {
+          $scope.menuItems = uiGridGridMenuService.getMenuItems( $scope );
+          $scope.$broadcast('show-menu');
+          $scope.shown = true;
+        }
+      };
+
+      $scope.$on('menu-hidden', function() {
+        $scope.shown = false;
+        gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
+      });
+    }
+  };
+
+}]);
+
+})();
+
+(function(){
+
+/**
+ * @ngdoc directive
+ * @name ui.grid.directive:uiGridMenu
+ * @element style
+ * @restrict A
+ *
+ * @description
+ * Allows us to interpolate expressions in `<style>` elements. Angular doesn't do this by default as it can/will/might? break in IE8.
+ *
+ * @example
+ <doc:example module="app">
+ <doc:source>
+ <script>
+ var app = angular.module('app', ['ui.grid']);
+
+ app.controller('MainCtrl', ['$scope', function ($scope) {
+
+ }]);
+ </script>
+
+ <div ng-controller="MainCtrl">
+   <div ui-grid-menu shown="true"  ></div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ </doc:scenario>
+ </doc:example>
+ */
+angular.module('ui.grid')
+
+.directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
+function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
+  var uiGridMenu = {
+    priority: 0,
+    scope: {
+      // shown: '&',
+      menuItems: '=',
+      autoHide: '=?'
+    },
+    require: '?^uiGrid',
+    templateUrl: 'ui-grid/uiGridMenu',
+    replace: false,
+    link: function ($scope, $elm, $attrs, uiGridCtrl) {
+
+      $scope.dynamicStyles = '';
+
+      var setupHeightStyle = function(gridHeight) {
+        //menu appears under header row, so substract that height from it's total
+        // additional 20px for general padding
+        var gridMenuMaxHeight = gridHeight - uiGridCtrl.grid.headerHeight - 20;
+        $scope.dynamicStyles = [
+          '.grid' + uiGridCtrl.grid.id + ' .ui-grid-menu-mid {',
+          'max-height: ' + gridMenuMaxHeight + 'px;',
+          '}'
+        ].join(' ');
+      };
+
+      if (uiGridCtrl) {
+        setupHeightStyle(uiGridCtrl.grid.gridHeight);
+        uiGridCtrl.grid.api.core.on.gridDimensionChanged($scope, function(oldGridHeight, oldGridWidth, newGridHeight, newGridWidth) {
+          setupHeightStyle(newGridHeight);
+		});
+      }
+
+      $scope.i18n = {
+        close: i18nService.getSafeText('columnMenu.close')
+      };
+
+    // *** Show/Hide functions ******
+      $scope.showMenu = function(event, args) {
+        if ( !$scope.shown ){
+
+          /*
+           * In order to animate cleanly we remove the ng-if, wait a digest cycle, then
+           * animate the removal of the ng-hide.  We can't successfully (so far as I can tell)
+           * animate removal of the ng-if, as the menu items aren't there yet.  And we don't want
+           * to rely on ng-show only, as that leaves elements in the DOM that are needlessly evaluated
+           * on scroll events.
+           *
+           * Note when testing animation that animations don't run on the tutorials.  When debugging it looks
+           * like they do, but angular has a default $animate provider that is just a stub, and that's what's
+           * being called.  ALso don't be fooled by the fact that your browser has actually loaded the
+           * angular-translate.js, it's not using it.  You need to test animations in an external application.
+           */
+          $scope.shown = true;
+
+          $timeout( function() {
+            $scope.shownMid = true;
+            $scope.$emit('menu-shown');
+          });
+        } else if ( !$scope.shownMid ) {
+          // we're probably doing a hide then show, so we don't need to wait for ng-if
+          $scope.shownMid = true;
+          $scope.$emit('menu-shown');
+        }
+
+        var docEventType = 'click';
+        if (args && args.originalEvent && args.originalEvent.type && args.originalEvent.type === 'touchstart') {
+          docEventType = args.originalEvent.type;
+        }
+
+        // Turn off an existing document click handler
+        angular.element(document).off('click touchstart', applyHideMenu);
+        $elm.off('keyup', checkKeyUp);
+        $elm.off('keydown', checkKeyDown);
+
+        // Turn on the document click handler, but in a timeout so it doesn't apply to THIS click if there is one
+        $timeout(function() {
+          angular.element(document).on(docEventType, applyHideMenu);
+          $elm.on('keyup', checkKeyUp);
+          $elm.on('keydown', checkKeyDown);
+
+        });
+        //automatically set the focus to the first button element in the now open menu.
+        gridUtil.focus.bySelector($elm, 'button[type=button]', true);
+      };
+
+
+      $scope.hideMenu = function(event) {
+        if ( $scope.shown ){
+          /*
+           * In order to animate cleanly we animate the addition of ng-hide, then use a $timeout to
+           * set the ng-if (shown = false) after the animation runs.  In theory we can cascade off the
+           * callback on the addClass method, but it is very unreliable with unit tests for no discernable reason.
+           *
+           * The user may have clicked on the menu again whilst
+           * we're waiting, so we check that the mid isn't shown before applying the ng-if.
+           */
+          $scope.shownMid = false;
+          $timeout( function() {
+            if ( !$scope.shownMid ){
+              $scope.shown = false;
+              $scope.$emit('menu-hidden');
+            }
+          }, 200);
+        }
+
+        angular.element(document).off('click touchstart', applyHideMenu);
+        $elm.off('keyup', checkKeyUp);
+        $elm.off('keydown', checkKeyDown);
+      };
+
+      $scope.$on('hide-menu', function (event, args) {
+        $scope.hideMenu(event, args);
+      });
+
+      $scope.$on('show-menu', function (event, args) {
+        $scope.showMenu(event, args);
+      });
+
+
+    // *** Auto hide when click elsewhere ******
+      var applyHideMenu = function(){
+        if ($scope.shown) {
+          $scope.$apply(function () {
+            $scope.hideMenu();
+          });
+        }
+      };
+
+      // close menu on ESC and keep tab cyclical
+      var checkKeyUp = function(event) {
+        if (event.keyCode === 27) {
+          $scope.hideMenu();
+        }
+      };
+
+      var checkKeyDown = function(event) {
+        var setFocus = function(elm) {
+          elm.focus();
+          event.preventDefault();
+          return false;
+        };
+        if (event.keyCode === 9) {
+          var firstMenuItem, lastMenuItem;
+          var menuItemButtons = $elm[0].querySelectorAll('button:not(.ng-hide)');
+          if (menuItemButtons.length > 0) {
+            firstMenuItem = menuItemButtons[0];
+            lastMenuItem = menuItemButtons[menuItemButtons.length - 1];
+            if (event.target === lastMenuItem && !event.shiftKey) {
+              setFocus(firstMenuItem);
+            } else if (event.target === firstMenuItem && event.shiftKey) {
+              setFocus(lastMenuItem);
+            }
+          }
+        }
+      };
+
+      if (typeof($scope.autoHide) === 'undefined' || $scope.autoHide === undefined) {
+        $scope.autoHide = true;
+      }
+
+      if ($scope.autoHide) {
+        angular.element($window).on('resize', applyHideMenu);
+      }
+
+      $scope.$on('$destroy', function () {
+        angular.element(document).off('click touchstart', applyHideMenu);
+      });
+
+
+      $scope.$on('$destroy', function() {
+        angular.element($window).off('resize', applyHideMenu);
+      });
+
+      if (uiGridCtrl) {
+       $scope.$on('$destroy', uiGridCtrl.grid.api.core.on.scrollBegin($scope, applyHideMenu ));
+      }
+
+      $scope.$on('$destroy', $scope.$on(uiGridConstants.events.ITEM_DRAGGING, applyHideMenu ));
+    }
+  };
+
+  return uiGridMenu;
+}])
+
+.directive('uiGridMenuItem', ['gridUtil', '$compile', 'i18nService', function (gridUtil, $compile, i18nService) {
+  var uiGridMenuItem = {
+    priority: 0,
+    scope: {
+      name: '=',
+      active: '=',
+      action: '=',
+      icon: '=',
+      shown: '=',
+      context: '=',
+      templateUrl: '=',
+      leaveOpen: '=',
+      screenReaderOnly: '='
+    },
+    require: ['?^uiGrid'],
+    templateUrl: 'ui-grid/uiGridMenuItem',
+    replace: false,
+    compile: function() {
+      return {
+        pre: function ($scope, $elm) {
+          if ($scope.templateUrl) {
+            gridUtil.getTemplate($scope.templateUrl)
+                .then(function (contents) {
+                  var template = angular.element(contents);
+
+                  var newElm = $compile(template)($scope);
+                  $elm.replaceWith(newElm);
+                });
+          }
+        },
+        post: function ($scope, $elm, $attrs, controllers) {
+          var uiGridCtrl = controllers[0];
+
+          // TODO(c0bra): validate that shown and active are functions if they're defined. An exception is already thrown above this though
+          // if (typeof($scope.shown) !== 'undefined' && $scope.shown && typeof($scope.shown) !== 'function') {
+          //   throw new TypeError("$scope.shown is defined but not a function");
+          // }
+          if (typeof($scope.shown) === 'undefined' || $scope.shown === null) {
+            $scope.shown = function() { return true; };
+          }
+
+          $scope.itemShown = function () {
+            var context = {};
+            if ($scope.context) {
+              context.context = $scope.context;
+            }
+
+            if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
+              context.grid = uiGridCtrl.grid;
+            }
+
+            return $scope.shown.call(context);
+          };
+
+          $scope.itemAction = function($event,title) {
+            $event.stopPropagation();
+
+            if (typeof($scope.action) === 'function') {
+              var context = {};
+
+              if ($scope.context) {
+                context.context = $scope.context;
+              }
+
+              // Add the grid to the function call context if the uiGrid controller is present
+              if (typeof(uiGridCtrl) !== 'undefined' && uiGridCtrl) {
+                context.grid = uiGridCtrl.grid;
+              }
+
+   

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[03/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/qdrChartService.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/qdrChartService.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/qdrChartService.js
new file mode 100644
index 0000000..992e80d
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/qdrChartService.js
@@ -0,0 +1,1109 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+  'use strict';
+
+    // The QDR chart service handles periodic gathering data for charts and displaying the charts
+  angular
+    .module('horizon.dashboard.dispatch')
+    .factory('horizon.dashboard.dispatch.chartService', QDRChartService);
+
+  QDRChartService.$inject = [
+    '$rootScope',
+    'horizon.dashboard.dispatch.comService',
+    '$http',
+    '$location'
+  ];
+
+  function QDRChartService(
+    $rootScope,
+    QDRService,
+    $http,
+    $location) {
+      var instance = 0;   // counter for chart instances
+      var bases = [];
+      var findBase = function (name, attr, request) {
+        for (var i=0; i<bases.length; ++i) {
+          var 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) {
+
+        var 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 = "#cbe7f3"; // 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 : 10;  // number of minutes of data to show (<= base.duration)
+        this.userTitle = null;      // user title overrides title()
+
+        // generate a unique id for this chart
+        this.id = function () {
+          var name = this.name()
+          var nameparts = name.split('/');
+          if (nameparts.length == 2)
+              name = nameparts[1];
+          var key = (QDRService.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.title = function (_) {
+          var name = this.request().aggregate ? 'Aggregate' : (QDRService.nameFromId(this.nodeId()) || this.nodeId());
+          var computed = name +
+             " " + QDRService.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 () {
+          var 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
+        // copy the savable properties to an object
+
+        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) {
+            var 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 () {
+          var 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
+      var 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.addDisconnectAction( function () {
+            self.charts.forEach( function (chart) {
+             self.unRegisterChart(chart, true)
+            })
+            QDRService.addConnectAction(self.init);
+          })
+        },
+
+        findChartRequest: function (nodeId, entity, aggregate) {
+          var 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 (var i=0; i<self.chartRequests.length; ++i) {
+            var 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) {
+          var foundBases = 0;
+          for (var i=0; i<self.charts.length; ++i) {
+            var 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) {
+            var baseIndex = bases.indexOf(chart.base)
+            bases.splice(baseIndex, 1);
+          }
+        },
+
+        // create a chart that doesn't get saved/loaded and has it's own
+        // request scheduler
+        createRequestAndChart: function (opts) {
+          var request = new ChartRequest(opts)
+          var chart = new Chart(opts, request)
+          return chart
+        },
+
+        // opts are nodeId, entity, name, attr, interval, instance, use_instance,
+        // forceCreate, aggregate, hdash
+        registerChart: function (opts) {
+          var 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 = new ChartRequest(opts); //nodeId, entity, name, attr, interval, aggregate);
+            self.chartRequests.push(request);
+            self.startCollecting(request);
+          }
+          var charts = self.findCharts(opts); //name, attr, nodeId, entity, hdash);
+          var 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 (var i=0; i<self.charts.length; ++i) {
+            var c = self.charts[i];
+            if (chart.equals(c)) {
+              var request = chart.request();
+              self.delChart(chart, skipSave);
+              if (request) {
+                // see if any other charts use this attr
+                for (var i=0; i<self.charts.length; ++i) {
+                  var c = self.charts[i];
+                  if (c.attr() == chart.attr() && c.request().equals(chart.request()))
+                    return;
+                }
+                // no other charts use this attr, so remove it
+                if (request.removeAttr(chart.name(), chart.attr()) == 0) {
+                  self.stopCollecting(request);
+                  self.delChartRequest(request);
+                }
+              }
+            }
+          }
+          if (!skipSave)
+            self.saveCharts();
+        },
+
+        stopCollecting: function (request) {
+          if (request.setTimeoutHandle) {
+            clearTimeout(request.setTimeoutHandle);
+            request.setTimeoutHandle = null;
+          }
+        },
+
+        startCollecting: function (request) {
+          // Using setTimeout instead of setInterval because the response may take longer than interval
+          request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request);
+        },
+
+        shouldRequest: function (request) {
+          // 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, once) {
+          if (!once && !self.shouldRequest(request)) {
+            request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request)
+            return;
+          }
+          // ensure the response has the name field so we can associate the response values with the correct chart
+          var attrs = request.attrs();
+          attrs.push("name");
+
+          // this is called when the response is received
+          var saveResponse = function (nodeId, entity, response) {
+            if (!response || !response.attributeNames)
+              return;
+            //QDR.log.debug("got chart results for " + nodeId + " " + entity);
+            // records is an array that has data for all names
+            var records = response.results;
+            if (!records)
+              return;
+
+            var now = new Date();
+            var cutOff = new Date(now.getTime() - request.duration * 60 * 1000);
+            // index of the "name" attr in the response
+            var nameIndex = response.attributeNames.indexOf("name");
+            if (nameIndex < 0)
+              return;
+
+            var names = request.names();
+            // for each record returned, find the name/attr for this request and save the data with this timestamp
+            for (var i=0; i<records.length; ++i) {
+              var 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) {
+                  var data = request.data(name, attr) // get a reference to the data array
+                  if (data) {
+                    var attrIndex = response.attributeNames.indexOf(attr)
+                    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();
+                    }
+                  }
+                })
+              }
+            }
+          }
+          if (request.aggregate) {
+            var nodeList = QDRService.nodeIdList()
+            QDRService.getMultipleNodeInfo(nodeList, request.entity, attrs, saveResponse, request.nodeId);
+          } else {
+            QDRService.getNodeInfo(request.nodeId, request.entity, attrs, saveResponse);
+          }
+          // it is now safe to schedule another request
+          if (once)
+            return;
+          request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request)
+        },
+
+        numCharts: function () {
+         return self.charts.filter( function (chart) { return chart.dashboard }).length;
+         //return self.charts.length;
+        },
+
+        isAttrCharted: function (nodeId, entity, name, attr) {
+          var 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)
+          });
+        },
+
+        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 () {
+          var charts = [];
+          var minCharts = [];
+
+          self.charts.forEach(function (chart) {
+            var 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 () {
+          var charts = angular.fromJson(localStorage["QDRCharts"]);
+          if (charts) {
+            // get array of known ids
+            var nodeList = QDRService.nodeList().map( function (node) {
+              return node.id;
+            })
+            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 = 10;
+                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;
+                var 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 : "#cbe7f3";
+                newChart.lineColor = chart.lineColor ? chart.lineColor : "#058dc7";
+                newChart.duration(chart.duration);
+                newChart.visibleDuration = chart.visibleDuration ? chart.visibleDuration : 10;
+                if (chart.userTitle)
+                  newChart.title(chart.userTitle);
+              }
+            })
+          }
+        },
+
+        AreaChart: function (chart) {
+          if (!chart)
+            return;
+
+          // if this is an aggregate chart, show it stacked
+          var stacked = chart.request().aggregate;
+          this.chart = chart; // reference to underlying chart
+          this.svgchart = null;
+          this.url = $location.absUrl();
+
+          // callback function. called by svgchart when binding data
+          // the variable 'this' refers to the svg and not the AreaChart,
+          // but since we are still in the scope of the AreaChart we have access to the passed in chart argument
+          this.chartData = function () {
+
+            var now = new Date();
+            var visibleDate = new Date(now.getTime() - chart.visibleDuration * 60 * 1000);
+            var data = chart.data();
+            var nodeList = QDRService.nodeIdList();
+
+            if (chart.type == "rate") {
+              var rateData = [];
+              var datalen = data.length;
+              var k = 0;  // inner loop optimization
+              for (var i=0; i<datalen; ++i) {
+                var d = data[i];
+                if (d[0] >= visibleDate) {
+                  for (var j=k+1; j<datalen; ++j) {
+                    var d1 = data[j];
+                    if (d1[0] - d[0] >= chart.rateWindow) { // rateWindow is the timespan to calculate rates
+                      var elapsed = Math.max((d1[0] - d[0]) / 1000, 1); // number of seconds that elapsed
+                      var rd = [d1[0],(d1[1] - d[1])/elapsed]
+                      k = j; // start here next time
+                      // this is a stacked (aggregate) chart
+                      if (stacked) {
+                        var detail = [];
+                        nodeList.forEach( function (node, nodeIndex) {
+                          if (d1[2][nodeIndex] && d[2][nodeIndex])
+                            detail.push({node: QDRService.nameFromId(node), val: (d1[2][nodeIndex].val- d[2][nodeIndex].val)/elapsed})
+                          })
+                        rd.push(detail)
+                      }
+                      rateData.push(rd);
+                      break;
+                    }
+                  }
+                }
+              }
+              // we need at least a point to chart
+              if (rateData.length == 0) {
+                rateData[0] = [chart.data()[0][0],0,[{node:'',val:0}]];
+              }
+              return rateData;
+            }
+            if (chart.visibleDuration != chart.duration()) {
+              return data.filter(function (d) { return d[0]>=visibleDate});
+            } else
+              return data;
+          }
+
+          this.zoom = function (id, zoom) {
+            if (this.svgchart) {
+              this.svgchart.attr("zoom", zoom)
+              d3.select('#' + id)
+                .data([this.chartData()])
+                .call(this.svgchart)
+            }
+          }
+
+          // called by the controller on the page that displays the chart
+          // called whenever the controller wants to redraw the chart
+          // note: the data is collected independently of how often the chart is redrawn
+          this.tick = function (id) {
+
+            // can't draw charts that don't have data yet
+            if (this.chart.data().length == 0) {
+              return;
+            }
+
+            // if we haven't created the svg yet
+            if (!this.svgchart) {
+
+              // make sure the dom element exists on the page
+              var div = angular.element('#' + id);
+              if (!div)
+                return;
+
+              var width = div.width();
+              var height = div.height();
+
+              // make sure the dom element has a size. otherwise we wouldn't see anything anyway
+              if (!width)
+                return;
+
+              var tooltipGenerator;
+              // stacked charts have a different tooltip
+              if (stacked) {
+                tooltipGenerator = function (d, color, format) {
+                  var html = "<table class='fo-table'><tbody><tr class='fo-title'>"+
+                  "<td align='center' colspan='2' nowrap>Time: "+d[0].toTimeString().substring(0, 8)+"</td></tr>"
+                  d[2].forEach( function (detail) {
+                      html += "<tr class='detail'><td align='right' nowrap>"
+                      + detail.node
+                      + "<div class='fo-table-legend' style='background-color: "+color(detail.node)+"'></div>"
+                      + "</td><td>"+format(detail.val)+"</td></tr>"
+                  })
+                  html += "</tbody></table>"
+                  return html;
+                }
+              } else {
+                tooltipGenerator = function (d, color, format) {
+                  var html = "<table class='fo-table'><tbody><tr class='fo-title'>"+
+                    "<td align='center'>Time</td><td align='center'>Value</td></tr><tr><td>" +
+                        d[0].toTimeString().substring(0, 8) +
+                    "</td><td>" +
+                       format(d[1]) +
+                    "</td></tr></tbody></table>"
+                  return html;
+                }
+              }
+              // create and initialize the chart
+              this.svgchart = self.timeSeriesStackedChart(id, width, height,
+                QDRService.humanify(this.chart.attr()),
+                this.chart.name(),
+                QDRService.nameFromId(this.chart.nodeId()) || '',
+                this.chart.entity(),
+                stacked,
+                this.chart.visibleDuration)
+              .tooltipGenerator(tooltipGenerator);
+            }
+
+            // in case the chart properties have changed, set the new props
+            this.svgchart
+              .attr("type", this.chart.type)
+              .attr("areaColor", this.chart.areaColor)
+              .attr("lineColor", this.chart.lineColor)
+              .attr("url", this.url)
+              .attr("title", this.chart.userTitle);
+
+            // bind the new data and update the chart
+            d3.select('#' + id)         // the div id on the page/dialog
+              .data([this.chartData()])
+              .call(this.svgchart);       // the charting function
+          }
+        },
+
+      timeSeriesStackedChart: function (id, width, height, attrName, name, node, entity, stacked, visibleDuration) {
+        var margin = {top: 20, right: 18, bottom: 10, left: 15}
+        // attrs that can be changed after the chart is created by using
+        // chart.attr(<attrname>, <attrvalue>);
+        var attrs = {
+              attrName: attrName, // like Deliveries to Container. Put at top of chart
+              name: name,         // like router.address/qdrhello  Put at bottom of chart with node
+              node: node,         // put at bottom of chart with name
+              entity: entity,     // like .router.address  Not used atm
+              title: "",          // user title overrides the node and name at the bottom of the chart
+              url: "",            // needed to reference filters and clip because of angular's location service
+              type: "value",      // value or rate
+              areaColor: "",      // can be set for non-stacked charts
+              lineColor: "",      // can be set for non-stacked charts
+              zoom: false,         // should the y-axis range start at 0 or the min data value
+              visibleDuration: visibleDuration
+        }
+        var width = width - margin.left - margin.right,
+          height = height - margin.top - margin.bottom,
+          yAxisTransitionDuration = 0
+
+        var x = d3.time.scale()
+          var y = d3.scale.linear()
+                .rangeRound([height, 0]);
+                // The x-accessor for the path generator; xScale * xValue.
+        var X = function (d) { return x(d[0]) }
+                // The x-accessor for the path generator; yScale * yValue.
+                var Y = function Y(d) { return y(d[1]) }
+
+                var xAxis = d3.svg.axis().scale(x).orient("bottom")
+          .outerTickSize(6)
+          .innerTickSize(-(height-margin.top-margin.bottom))
+                    .tickPadding(2)
+                    .ticks(d3.time.minutes, 2)
+                var yAxis = d3.svg.axis().scale(y).orient("right")
+                    .outerTickSize(8)
+                    .innerTickSize(-(width-margin.left-margin.right))
+                    .tickPadding(10)
+                    .ticks(3)
+                    .tickFormat(function(d) { return formatValue(d)})
+
+        var tooltipGenerator = function (d, color, format) {return ""}; // should be overridden to set an appropriate tooltip
+                var formatValue = d3.format(".2s");
+                var formatPrecise = d3.format(",");
+                var bisectDate = d3.bisector(function(d) { return d[0]; }).left;
+        var line = d3.svg.line();
+
+            var stack = d3.layout.stack()
+                .offset("zero")
+                .values(function (d) { return d.values; })
+                .x(function (d) { return x(d.date); })
+                .y(function (d) { return d.value; });
+
+          var area = d3.svg.area()
+        if (stacked) {
+              area.interpolate("cardinal")
+                .x(function (d) { return x(d.date); })
+                .y0(function (d) { return y(d.y0); })
+                .y1(function (d) { return y(d.y0 + d.y); });
+        } else {
+                    area.interpolate("basis").x(X).y1(Y)
+                    line.x(X).y(Y)
+        }
+        var color = d3.scale.category20();
+
+          var sv = d3.select("#"+id).append("svg")
+                .attr("width",  width  + margin.left + margin.right)
+                .attr("height", height + margin.top  + margin.bottom)
+        var svg = sv
+              .append("g")
+                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+        sv.append("linearGradient")
+              .attr("id", id) //"temperature-gradient")
+              .attr("gradientUnits", "userSpaceOnUse")
+              .attr("x1", 0).attr("y1", height *.5 )
+              .attr("x2", 0).attr("y2", height * 1.2)
+            .selectAll("stop")
+              .data([
+                {offset: "0%", opacity: 1},
+                {offset: "100%", opacity: 0}
+              ])
+            .enter().append("stop")
+              .attr("offset", function(d) { return d.offset; })
+              .attr("stop-opacity", function(d) { return d.opacity; })
+              .attr("stop-color", function(d) { return "#cbe7f3" });
+/*
+            var clip = svg.append("defs").append("svg:clipPath")
+              .attr("id", "clip")
+              .append("svg:rect")
+              .attr("id", "clip-rect")
+              .attr("x", "0")
+              .attr("y", "0")
+              .attr("width", width)
+              .attr("height", height);
+*/
+        // we want all our areas to appear before the axiis
+        svg.append("g")
+          .attr("class", "section-container")
+
+            svg.append("g")
+                  .attr("class", "x axis")
+
+            svg.append("g")
+                  .attr("class", "y axis")
+
+                svg.append("text").attr("class", "title")
+                        .attr("x", (width / 2) - (margin.left + margin.right) / 2)
+                        .attr("y", 0 - (margin.top / 2))
+                        .attr("text-anchor", "middle")
+                        .text(attrs.attrName);
+
+                svg.append("text").attr("class", "legend")
+                        .attr("x", (width / 2) - (margin.left + margin.right) / 2)
+                        .attr("y", height + (margin.bottom / 2) )
+                        .attr("text-anchor", "middle")
+                        .text(!stacked ? attrs.node + " " + attrs.name : attrs.name);
+
+                var focus = sv.append("g")
+                  .attr("class", "focus")
+                  .style("display", "none");
+
+                focus.append("circle")
+                  .attr("r", 4.5);
+
+                var focusg = focus.append("g");
+                focusg.append("rect")
+                    .attr("class", "mo-guide y")
+                    .attr("width", 1)
+                    .attr("height", height - (margin.top + margin.bottom));
+                focusg.append("rect")
+                    .attr("class", "mo-guide x")
+                    .attr("width", width - (margin.left + margin.right))
+                    .attr("height", 1);
+        focus.append("foreignObject")
+          .attr('class', 'svg-tooltip')
+          .append("xhtml:span");
+/*
+        var transition = d3.select({}).transition()
+            .duration(2000)
+            .ease("linear");
+*/
+                function chart(selection) {
+                    selection.each(function(data) {
+
+          var seriesArr = []
+          if (stacked) {
+            var detailNames = data[0][2].map(function (detail){ return detail.node })
+            var revNames = angular.copy(detailNames).reverse();
+            color.domain(revNames);
+
+                var series = {};
+                detailNames.forEach(function (name) {
+              series[name] = {name: name, values:[]};
+              seriesArr.unshift(series[name]);    // insert at beginning
+                });
+
+                data.forEach(function (d) {
+              detailNames.map(function (name, i) {
+                series[name].values.push({date: d[0], value: d[2][i] ? d[2][i].val : 0});
+              });
+                });
+
+                // this decorates seriesArr with x,y,and y0 properties
+                stack(seriesArr);
+          }
+
+                    var extent = d3.extent(data, function(d) {return d[0];});
+                    //var points = data.length;
+          //var futureDate = new Date(data[points-1][0].getTime() - attrs.visibleDuration * 60 * 1000);
+                    //extent = [futureDate, data[points-1][0]]
+                    x.domain(extent)
+                      .range([0, width - margin.left - margin.right]);
+
+                    // Update the y-scale.
+                    var min = attrs.zoom ? 0 : d3.min(data, function(d) {return d[1]}) *.99;
+                    var max = d3.max(data, function(d) {return d[1]}) * 1.01;
+                    var mean = d3.mean(data, function(d) {return d[1]});
+                    //max = max * 1.01;
+                    var diff = (max - min);
+                    if (diff == 0) {
+                        max = max + 1;
+                        diff = 1;
+                    }
+                    var ratio = mean != 0 ? diff / mean : 1;
+                    if (ratio < .05)
+                        formatValue = d3.format(".3s")
+
+          if (stacked) {
+                      y.domain([min, max])
+                        .range([height - margin.top - margin.bottom, 0]);
+          } else {
+                        y
+                          .domain([min, max])
+                          .range([height - margin.top - margin.bottom, 0]);
+          }
+                        if (attrs.type == "rate") {
+                            area.interpolate("basis");  // rate charts look better smoothed
+                            line.interpolate("basis");
+                        }
+                        else {
+                            area.interpolate("linear"); // don't smooth value charts
+                            line.interpolate("linear");
+                        }
+
+                    // adjust the xaxis based on the range of x values (domain)
+                    var timeSpan = (extent[1] - extent[0]) / (1000 * 60);   // number of minutes
+                    if (timeSpan < 1.5)
+                        xAxis.ticks(d3.time.seconds, 10);
+                    else if (timeSpan < 3)
+                        xAxis.ticks(d3.time.seconds, 30);
+                    else if (timeSpan < 8)
+                        xAxis.ticks(d3.time.minutes, 1);
+                    else
+                        xAxis.ticks(d3.time.minutes, 2);
+
+                    // adjust the number of yaxis ticks based on the range of y values
+                    if (formatValue(min) === formatValue(max))
+                        yAxis.ticks(2);
+
+          var container = svg.select('.section-container');
+          container.selectAll('.series').remove();
+          if (stacked) {
+                      y.domain([Math.min(min, 0), d3.max(seriesArr, function (c) {
+                          return d3.max(c.values, function (d) { return d.y0 + d.y; });
+                        })]);
+
+            // creates a .series g path for each section in the detail
+            // since we don't get more sections this selection is only run once
+                      var series = container.selectAll(".series")
+                        .data(seriesArr)
+
+            series.enter().append("g")
+                          .attr("class", "series")
+                  .append("path")
+              .attr("class", "streamPath")
+              .style("fill", function (d) { return color(d.name); })
+              .style("stroke", "grey");
+
+            series.exit().remove()
+
+            // each time the data is updated, update each section
+            container.selectAll(".series .streamPath").data(seriesArr)
+              .attr("d", function (d) { return area(d.values); })
+          } else {
+                      var series = container.selectAll(".series")
+                        .data([data], function(d) { return d; })
+
+            var g = series.enter().append("g")
+              .attr("class", "series")
+
+                      g.append("path")
+                          .attr("class", "area")
+              .style("fill", "url(" + attrs.url + "#" + id + ") " + attrs.areaColor) //temperature-gradient)")
+                            .attr("d", area.y0(y.range()[0]))
+                            .attr("transform", null);
+
+                      g.append("path")
+                          .attr("class", "line")
+              .style("stroke", attrs.lineColor)
+                            .attr("d", line)
+/*
+debugger;
+            g.transition()
+              .duration(2000)
+                            .attr("transform", "translate(-4)");
+*/
+            series.exit().remove()
+
+            sv.selectAll("stop")
+                  .attr("stop-color", attrs.areaColor)
+
+          }
+                    // Update the x-axis.
+                    svg.select(".x.axis")
+                      .attr("transform", "translate(0," + (height - margin.top - margin.bottom + 1) + ")")
+                      .call(xAxis);
+
+                    svg.select(".y.axis")
+            .transition().duration(yAxisTransitionDuration)  // animate the y axis
+                      .attr("transform", "translate(" + (width - margin.right - margin.left) + ",0)")
+                      .call(yAxis);
+                    yAxisTransitionDuration = 1000  // only do a transition after the chart is 1st drawn
+
+                    // TODO: fix this
+                    // need to recreate this every update... not sure why
+                    var overlay = sv.select(".overlay");
+                    if (!overlay.empty())
+                            overlay.remove();
+                    sv.append("rect")
+                      .attr("class", "overlay")
+                      .attr("width", width)
+                      .attr("height", height)
+                      .on("mouseover", function () {focus.style("display", null)})
+                      .on("mouseout", function () {focus.style("display", "none")})
+                      .on("mousemove", mousemove)
+
+                      function mousemove() {
+                          var x0 = x.invert(d3.mouse(this)[0] - margin.left);
+                          var i = bisectDate(data, x0, 1);
+                          if (i < data.length && i > 0) {
+                              var d0 = data[i - 1];
+                              var d1 = data[i];
+                // set d to the data that is closest to the mouse position
+                              var d = x0 - d0[0] > d1[0] - x0 ? d1 : d0;
+                              focus.attr("transform", "translate(" + (x(d[0]) + margin.left) + "," + (y(d[1]) + margin.top) + ")");
+
+                var tipFormat = formatPrecise;
+                if (attrs.type === "rate")
+                  tipFormat = d3.format(".2n")
+                // set the tooltip html and position it
+                focus.select('.svg-tooltip span')
+                  .html(tooltipGenerator(d, color, tipFormat))
+
+                var foBounds = focus.select('table')[0][0].getBoundingClientRect();
+                              var mx = x(d[0]); // mouse x
+                              var my = y(d[1]); // mouse y
+
+                              // perfer to put the tooltip in the nw corner relative to the focus circle
+                              var foy = -foBounds.height;
+                var fox = -foBounds.width;
+                // off the left side
+                if (mx - foBounds.width - margin.left < 0)
+                  fox = 0;
+                // above the top
+                if (my - foBounds.height - margin.top < 0)
+                  foy = 0;
+                // won't fit above or below, just put it at bottom
+                if (my + foBounds.height > height)
+                  foy = -(foBounds.height - (height - my));
+
+                focus.select('.svg-tooltip')
+                  .attr('x', fox).attr('y', foy);
+
+                // position the guide lines
+                              focus.select(".mo-guide.y")
+                                  .attr("y", -my);
+                              focus.select(".mo-guide.x")
+                                  .attr("x", -mx);
+
+                          } else {
+                              focus.attr("transform", "translate(-10,-10)");
+                          }
+                      }
+
+                    })
+
+
+                }
+        chart.attr = function (attrName, value) {
+          if (arguments.length < 2)
+            return arguments.length == 1 ? attrs[attrName] : chart;
+          if (angular.isDefined(attrs[attrName]))
+            attrs[attrName] = value;
+          return chart;
+        }
+        chart.tooltipGenerator = function (_) {
+          tooltipGenerator = _;
+          return chart;
+        }
+
+        return chart;
+            }
+        }
+        return self;
+  };
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/config-file-header.html
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/config-file-header.html b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/config-file-header.html
new file mode 100644
index 0000000..b3cb671
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/config-file-header.html
@@ -0,0 +1,17 @@
+## 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
+##

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/download-dialog-template.html
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/download-dialog-template.html b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/download-dialog-template.html
new file mode 100644
index 0000000..1848278
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/download-dialog-template.html
@@ -0,0 +1,23 @@
+<div class="modal-header">
+    <h3 class="modal-title">Configure new router</h3>
+</div>
+<div class="modal-body">
+
+    <label title="Show descriptions and default values in confile files"><input type="checkbox" ng-model="verbose"> Verbose output</label>
+    <div>
+        <button ng-click="download()">Download</button>
+        <button class="btn" zero-clipboard data-clipboard-text="{{output}}" title="Copy to clipboard">
+            <i class="icon-copy"></i>
+        </button> configuration file for {{newRouterName}}
+    </div>
+    <div ng-repeat="part in parts">
+        <button ng-click="downloadPart(part)">Download</button>
+        <button class="btn" zero-clipboard data-clipboard-text="{{part.output}}" title="Copy to clipboard">
+            <i class="icon-copy"></i>
+        </button> connector section for {{part.name}}
+    </div>
+
+</div>
+<div class="modal-footer">
+    <button class="btn btn-primary" type="button" ng-click="done()">Done</button>
+</div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/node-config-template.html
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/node-config-template.html b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/node-config-template.html
new file mode 100644
index 0000000..5816dfc
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/topology/node-config-template.html
@@ -0,0 +1,51 @@
+<!--
+    This is the template for the node edit dialog that is displayed.
+-->
+<div class="modal-header">
+    <h3 class="modal-title">Configure new router</h3>
+</div>
+<div class="modal-body">
+    <form novalidate name="editForm">
+
+        <tabset vertical="true" class="tabs-left">
+            <tab ng-repeat="entity in entities"> <!-- ng-class="{separated: entity.tabName == 'listener0'}" -->
+                <tab-heading>
+                    <i ng-if="entity.icon !== ''" ng-class="entity.icon ? 'ui-icon-arrowthick-1-w' : 'ui-icon-arrowthick-1-e'" class="ui-icon"></i>{{entity.humanName}}
+                </tab-heading>
+                <div class="entity-description">{{entity.description}}</div>
+                <fieldset>
+                    <div ng-mouseenter="showDescription(attribute, $event)" ng-repeat="attribute in entity.attributes">
+                        <label for="{{attribute.name}}">{{attribute.humanName}}</label>
+                        <!-- we can't do <input type="{angular expression}"> because... jquery throws an exception because... -->
+                        <div ng-if="attribute.input == 'input'">
+                            <!-- ng-pattern="testPattern(attribute)" -->
+                            <input ng-if="attribute.type == 'number'" type="number" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.value" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/>
+                            <input ng-if="attribute.type == 'text'" type="text" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.value" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/>
+                        </div>
+                        <div ng-if="attribute.input == 'select'">
+                            <select id="{{attribute.name}}" ng-model="attribute.selected" ng-options="item for item in attribute.rawtype"></select>
+                        </div>
+                        <div ng-if="attribute.input == 'boolean'" class="boolean">
+                            <label><input type="radio" ng-model="attribute.value" value="true"> True</label>
+                            <label><input type="radio" ng-model="attribute.value" value="false"> False</label>
+                        </div>
+                    </div>
+                </fieldset>
+                <div class="attr-description">{{attributeDescription}}
+                    <div class="attr-type">{{attributeType}}</div>
+                    <div class="attr-required">{{attributeRequired}}</div>
+                    <div class="attr-unique">{{attributeUnique}}</div>
+                </div>
+                <div class="attr-annotations" ng-repeat="annotation in entity.annotatedBy">
+                    <span>You can also enter the <button ng-click="selectAnnotationTab(annotation)">{{annotation}}</button> values.</span>
+                </div>
+            </tab>
+        </tabset>
+
+
+    </form>
+</div>
+<div class="modal-footer">
+    <button class="btn btn-primary" type="button" ng-click="download()">Download</button>
+    <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
+</div>


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[06/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/slider.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/slider.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/slider.js
new file mode 100644
index 0000000..97189ae
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/slider.js
@@ -0,0 +1,233 @@
+/*
+ jQuery UI Slider plugin wrapper
+*/
+angular.module('ui.slider', []).value('uiSliderConfig',{}).directive('uiSlider', ['uiSliderConfig', '$timeout', function(uiSliderConfig, $timeout) {
+    uiSliderConfig = uiSliderConfig || {};
+    return {
+        require: 'ngModel',
+        compile: function() {
+            var preLink = function (scope, elm, attrs, ngModel) {
+
+                function parseNumber(n, decimals) {
+                    return (decimals) ? parseFloat(n) : parseInt(n, 10);
+                }
+
+                var directiveOptions = angular.copy(scope.$eval(attrs.uiSlider));
+                var options = angular.extend(directiveOptions || {}, uiSliderConfig);
+                // Object holding range values
+                var prevRangeValues = {
+                    min: null,
+                    max: null
+                };
+
+                // convenience properties
+                var properties = ['min', 'max', 'step', 'lowerBound', 'upperBound'];
+                var useDecimals = (!angular.isUndefined(attrs.useDecimals)) ? true : false;
+                var updateOn = (angular.isDefined(options['updateOn'])) ? options['updateOn'] : 'slide'
+
+                var init = function() {
+                    // When ngModel is assigned an array of values then range is expected to be true.
+                    // Warn user and change range to true else an error occurs when trying to drag handle
+                    if (angular.isArray(ngModel.$viewValue) && options.range !== true) {
+                        console.warn('Change your range option of ui-slider. When assigning ngModel an array of values then the range option should be set to true.');
+                        options.range = true;
+                    }
+
+                    // Ensure the convenience properties are passed as options if they're defined
+                    // This avoids init ordering issues where the slider's initial state (eg handle
+                    // position) is calculated using widget defaults
+                    // Note the properties take precedence over any duplicates in options
+                    angular.forEach(properties, function(property) {
+                        if (angular.isDefined(attrs[property])) {
+                            options[property] = parseNumber(attrs[property], useDecimals);
+                        }
+                    });
+
+                    elm.slider(options);
+                    init = angular.noop;
+                };
+
+                // Find out if decimals are to be used for slider
+                angular.forEach(properties, function(property) {
+                    // support {{}} and watch for updates
+                    attrs.$observe(property, function(newVal) {
+                        if (!!newVal) {
+                            init();
+                            options[property] = parseNumber(newVal, useDecimals);
+                            elm.slider('option', property, parseNumber(newVal, useDecimals));
+                            ngModel.$render();
+                        }
+                    });
+                });
+                attrs.$observe('disabled', function(newVal) {
+                    init();
+                    elm.slider('option', 'disabled', !!newVal);
+                });
+
+                // Watch ui-slider (byVal) for changes and update
+                scope.$watch(attrs.uiSlider, function(newVal) {
+                    init();
+                    if(newVal !== undefined) {
+                      elm.slider('option', newVal);
+                    }
+                }, true);
+
+                // Late-bind to prevent compiler clobbering
+                $timeout(init, 0, true);
+
+                // Update model value from slider
+                elm.bind(updateOn, function(event, ui) {
+                    var valuesChanged;
+
+                    if (ui.values) {
+                        var boundedValues = ui.values.slice();
+
+                        if (options.lowerBound && boundedValues[0] < options.lowerBound) {
+                            boundedValues[0] = Math.max(boundedValues[0], options.lowerBound);
+                        }
+                        if (options.upperBound && boundedValues[1] > options.upperBound) {
+                            boundedValues[1] = Math.min(boundedValues[1], options.upperBound);
+                        }
+
+                        if (boundedValues[0] !== ui.values[0] || boundedValues[1] !== ui.values[1]) {
+                            valuesChanged = true;
+                            ui.values = boundedValues;
+                        }
+                    } else {
+                        var boundedValue = ui.value;
+
+                        if (options.lowerBound && boundedValue < options.lowerBound) {
+                            boundedValue = Math.max(boundedValue, options.lowerBound);
+                        }
+                        if (options.upperBound && boundedValue > options.upperBound) {
+                            boundedValue = Math.min(boundedValue, options.upperBound);
+                        }
+
+                        if (boundedValue !== ui.value) {
+                            valuesChanged = true;
+                            ui.value = boundedValue;
+                        }
+                    }
+
+
+                    ngModel.$setViewValue(ui.values || ui.value);
+                    $(ui.handle).find('.ui-slider-tip').text(ui.value);
+                    scope.$apply();
+
+                    if (valuesChanged) {
+                        setTimeout(function() {
+                            elm.slider('value', ui.values || ui.value);
+                        }, 0);
+
+                        return false;
+                    }
+                });
+
+                // Update slider from model value
+                ngModel.$render = function() {
+                    init();
+                    var method = options.range === true ? 'values' : 'value';
+
+                    if (options.range !== true && isNaN(ngModel.$viewValue) && !(ngModel.$viewValue instanceof Array)) {
+                        ngModel.$viewValue = 0;
+                    }
+                    else if (options.range && !angular.isDefined(ngModel.$viewValue)) {
+                            ngModel.$viewValue = [0,0];
+                    }
+
+                    // Do some sanity check of range values
+                    if (options.range === true) {
+                        // previously, the model was a string b/c it was in a text input, need to convert to a array.
+                        // make sure input exists, comma exists once, and it is a string.
+                        if (ngModel.$viewValue && angular.isString(ngModel.$viewValue) && (ngModel.$viewValue.match(/,/g) || []).length === 1) {
+                            // transform string model into array.
+                            var valueArr = ngModel.$viewValue.split(',');
+                            ngModel.$viewValue = [Number(valueArr[0]), Number(valueArr[1])];
+                        }
+                        // Check outer bounds for min and max values
+                        if (angular.isDefined(options.min) && options.min > ngModel.$viewValue[0]) {
+                            ngModel.$viewValue[0] = options.min;
+                        }
+                        if (angular.isDefined(options.max) && options.max < ngModel.$viewValue[1]) {
+                            ngModel.$viewValue[1] = options.max;
+                        }
+
+                        // Check min and max range values
+                        if (ngModel.$viewValue[0] > ngModel.$viewValue[1]) {
+                            // Min value should be less to equal to max value
+                            if (prevRangeValues.min >= ngModel.$viewValue[1]) {
+                                ngModel.$viewValue[1] = prevRangeValues.min;
+                            }
+                            // Max value should be less to equal to min value
+                            if (prevRangeValues.max <= ngModel.$viewValue[0]) {
+                                ngModel.$viewValue[0] = prevRangeValues.max;
+                            }
+                        }
+
+                        // Store values for later user
+                        prevRangeValues.min = ngModel.$viewValue[0];
+                        prevRangeValues.max = ngModel.$viewValue[1];
+
+                    }
+                    elm.slider(method, ngModel.$viewValue);
+                };
+
+                scope.$watch(attrs.ngModel, function() {
+                    if (options.range === true) {
+                        ngModel.$render();
+
+                        $(elm).find('.ui-slider-tip').each(function(i, tipElm) {
+                            $(tipElm).text(ngModel.$viewValue[i]);
+                        });
+                    } else {
+                        $(elm).find('.ui-slider-tip').text(ngModel.$viewValue);
+                    }
+                }, true);
+
+                function destroy() {
+                    if (elm.hasClass('ui-slider')) {
+                        elm.slider('destroy');
+                    }
+                }
+
+                scope.$on("$destroy", destroy);
+                elm.one('$destroy', destroy);
+            };
+
+            var postLink = function (scope, element, attrs, ngModel) {
+                // Add tick marks if 'tick' and 'step' attributes have been setted on element.
+                // Support horizontal slider bar so far. 'tick' and 'step' attributes are required.
+                var options = angular.extend({}, scope.$eval(attrs.uiSlider));
+                var properties = ['min', 'max', 'step', 'tick', 'tip'];
+                angular.forEach(properties, function(property) {
+                    if (angular.isDefined(attrs[property])) {
+                        options[property] = attrs[property];
+                    }
+                });
+                if (angular.isDefined(options['tick']) && angular.isDefined(options['step'])) {
+                    var total = parseInt( (parseInt(options['max']) - parseInt(options['min'])) /parseInt(options['step']));
+                    for (var i = total; i >= 0; i--) {
+                        var left = ((i / total) * 100) + '%';
+                        $("<div/>").addClass("ui-slider-tick").appendTo(element).css({left: left});
+                    };
+                }
+                if(angular.isDefined(options['tip'])) {
+                    $timeout(function(){
+                        var handles = element.find('.ui-slider-handle');
+                        if(handles && handles.length>1 && ngModel.$viewValue && angular.isArray(ngModel.$viewValue)){
+                            $(handles[0]).append('<div class="ui-slider-tip">'+ngModel.$viewValue[0]+'</div>');
+                            $(handles[1]).append('<div class="ui-slider-tip">'+ngModel.$viewValue[1]+'</div>');
+                        }else{
+                            element.find('.ui-slider-handle').append('<div class="ui-slider-tip">'+ngModel.$viewValue+'</div>');
+                        }
+                    },10);
+                }
+            }
+
+            return {
+                pre: preLink,
+                post: postLink
+            };
+        }
+    };
+}]);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/tooltipsy.min.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/tooltipsy.min.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/tooltipsy.min.js
new file mode 100644
index 0000000..ed2c2f8
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/tooltipsy.min.js
@@ -0,0 +1,20 @@
+/* tooltipsy by Brian Cray
+ * Lincensed under GPL2 - http://www.gnu.org/licenses/gpl-2.0.html
+ * Option quick reference:
+ * - alignTo: "element" or "cursor" (Defaults to "element")
+ * - offset: Tooltipsy distance from element or mouse cursor, dependent on alignTo setting. Set as array [x, y] (Defaults to [0, -1])
+ * - content: HTML or text content of tooltip. Defaults to "" (empty string), which pulls content from target element's title attribute
+ * - show: function(event, tooltip) to show the tooltip. Defaults to a show(100) effect
+ * - hide: function(event, tooltip) to hide the tooltip. Defaults to a fadeOut(100) effect
+ * - delay: A delay in milliseconds before showing a tooltip. Set to 0 for no delay. Defaults to 200
+ * - css: object containing CSS properties and values. Defaults to {} to use stylesheet for styles
+ * - className: DOM class for styling tooltips with CSS. Defaults to "tooltipsy"
+ * - showEvent: Set a custom event to bind the show function. Defaults to mouseenter
+ * - hideEvent: Set a custom event to bind the show function. Defaults to mouseleave
+ * Method quick reference:
+ * - $('element').data('tooltipsy').show(): Force the tooltip to show
+ * - $('element').data('tooltipsy').hide(): Force the tooltip to hide
+ * - $('element').data('tooltipsy').destroy(): Remove tooltip from DOM
+ * More information visit http://tooltipsy.com/
+ */
+;(function(a){a.tooltipsy=function(c,b){this.options=b;this.$el=a(c);this.title=this.$el.attr("title")||"";this.$el.attr("title","");this.random=parseInt(Math.random()*10000);this.ready=false;this.shown=false;this.width=0;this.height=0;this.delaytimer=null;this.$el.data("tooltipsy",this);this.init()};a.tooltipsy.prototype={init:function(){var e=this,d,b=e.$el,c=b[0];e.settings=d=a.extend({},e.defaults,e.options);d.delay=+d.delay;if(typeof d.content==="function"){e.readify()}if(d.showEvent===d.hideEvent&&d.showEvent==="click"){b.toggle(function(f){if(d.showEvent==="click"&&c.tagName=="A"){f.preventDefault()}if(d.delay>0){e.delaytimer=window.setTimeout(function(){e.show(f)},d.delay)}else{e.show(f)}},function(f){if(d.showEvent==="click"&&c.tagName=="A"){f.preventDefault()}window.clearTimeout(e.delaytimer);e.delaytimer=null;e.hide(f)})}else{b.bind(d.showEvent,function(f){if(d.showEvent==="click"&&c.tagName=="A"){f.preventDefault()}e.delaytimer=window.setTimeout(function(){e.show(f)},d.d
 elay||0)}).bind(d.hideEvent,function(f){if(d.showEvent==="click"&&c.tagName=="A"){f.preventDefault()}window.clearTimeout(e.delaytimer);e.delaytimer=null;e.hide(f)})}},show:function(i){if(this.ready===false){this.readify()}var b=this,f=b.settings,h=b.$tipsy,k=b.$el,d=k[0],g=b.offset(d);if(b.shown===false){if((function(m){var l=0,e;for(e in m){if(m.hasOwnProperty(e)){l++}}return l})(f.css)>0){b.$tip.css(f.css)}b.width=h.outerWidth();b.height=h.outerHeight()}if(f.alignTo==="cursor"&&i){var j=[i.clientX+f.offset[0],i.clientY+f.offset[1]];if(j[0]+b.width>a(window).width()){var c={top:j[1]+"px",right:j[0]+"px",left:"auto"}}else{var c={top:j[1]+"px",left:j[0]+"px",right:"auto"}}}else{var j=[(function(){if(f.offset[0]<0){return g.left-Math.abs(f.offset[0])-b.width}else{if(f.offset[0]===0){return g.left-((b.width-k.outerWidth())/2)}else{return g.left+k.outerWidth()+f.offset[0]}}})(),(function(){if(f.offset[1]<0){return g.top-Math.abs(f.offset[1])-b.height}else{if(f.offset[1]===0){return g.to
 p-((b.height-b.$el.outerHeight())/2)}else{return g.top+b.$el.outerHeight()+f.offset[1]}}})()]}h.css({top:j[1]+"px",left:j[0]+"px"});b.settings.show(i,h.stop(true,true))},hide:function(c){var b=this;if(b.ready===false){return}if(c&&c.relatedTarget===b.$tip[0]){b.$tip.bind("mouseleave",function(d){if(d.relatedTarget===b.$el[0]){return}b.settings.hide(d,b.$tipsy.stop(true,true))});return}b.settings.hide(c,b.$tipsy.stop(true,true))},readify:function(){this.ready=true;this.$tipsy=a('<div id="tooltipsy'+this.random+'" style="position:fixed;z-index:2147483647;display:none">').appendTo("body");this.$tip=a('<div class="'+this.settings.className+'">').appendTo(this.$tipsy);this.$tip.data("rootel",this.$el);var c=this.$el;var b=this.$tip;this.$tip.html(this.settings.content!=""?(typeof this.settings.content=="string"?this.settings.content:this.settings.content(c,b)):this.title)},offset:function(b){return this.$el[0].getBoundingClientRect()},destroy:function(){if(this.$tipsy){this.$tipsy.remove
 ();a.removeData(this.$el,"tooltipsy")}},defaults:{alignTo:"element",offset:[0,-1],content:"",show:function(c,b){b.fadeIn(100)},hide:function(c,b){b.fadeOut(100)},css:{},className:"tooltipsy",delay:200,showEvent:"mouseenter",hideEvent:"mouseleave"}};a.fn.tooltipsy=function(b){return this.each(function(){new a.tooltipsy(this,b)})}})(jQuery);
\ 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


[07/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/rhea-min.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/rhea-min.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/rhea-min.js
new file mode 100644
index 0000000..939f401
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/lib/rhea-min.js
@@ -0,0 +1,4 @@
+require=function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){(function(c,d){"use strict";function e(a,b,c){Error.call(this),Error.captureStackTrace(this,this.constructor),this.message=a,this.condition=b,this.description=a,this.connection=c}function f(a){return a.get_id_string?a.get_id_string():a.localAddress+":"+a.localPort+" -> "+a.remoteAddress+":"+a.remotePort}function g(a){var b=null;return{get_session:function(){return b||(b=a.create_session(),b.begin()),b}}}function h(a,b){if(a){var c,d=a;return function(e){return c!==e&&(d=a,c=e),d--?b(e):-1}}return b}function i(a,b){var c,d=a;re
 turn function(e){c!==e&&(d=a,c=e);var f=d,g=2*d;return d=b>g?g:b,f}}function j(a){if(void 0===a.transport||"tcp"===a.transport)return u.connect;if("tls"===a.transport||"ssl"===a.transport)return v.connect;throw Error("Unrecognised transport: "+a.transport)}function k(a){var b={};return b.connect=a.connect?a.connect:j(a),b.host=a.host?a.host:"localhost",b.port=a.port?a.port:5672,b.options=a,b}function l(a){return function(){return this.remote.open?this.remote.open[a]:void 0}}function m(a){A.prototype["on_"+a]=function(b){var c=this.remote_channel_map[b.channel];if(!c)throw Error(a+" received on invalid channel "+b.channel);c["on_"+a](b)}}var n=a("./frames.js"),o=a("./log.js"),p=a("./sasl.js"),q=a("./util.js"),r=a("./endpoint.js"),s=a("./session.js"),t=a("./transport.js"),u=a("net"),v=a("tls"),w=a("events").EventEmitter,x=0;a("util").inherits(e,Error);var y=["container_id","hostname","max_frame_size","channel_max","idle_time_out","outgoing_locales","incoming_locales","offered_capabili
 ties","desired_capabilities","properties"],z=1,A=function(a,b){if(this.options={},a)for(var c in a)this.options[c]=a[c];if(this.container=b,this.options.id||(this.options.id="connection-"+z++),this.options.container_id||(this.options.container_id=b?b.id:q.generate_uuid()),!this.options.connection_details){var d=this;this.options.connection_details=function(){return k(d.options)}}var e=this.get_option("reconnect",!0);if("boolean"==typeof e&&e){var f=this.get_option("initial_reconnect_delay",100),j=this.get_option("max_reconnect_delay",6e4);this.options.reconnect=h(this.get_option("reconnect_limit"),i(f,j))}else if("number"==typeof e){var m=this.options.reconnect;this.options.reconnect=h(this.get_option("reconnect_limit"),function(){return m})}this.registered=!1,this.state=new r,this.local_channel_map={},this.remote_channel_map={},this.local={},this.remote={},this.local.open=n.open(this.options),this.local.close=n.close({}),this.session_policy=g(this),this.amqp_transport=new t(this.op
 tions.id,x,n.TYPE_AMQP,this),this.sasl_transport=void 0,this.transport=this.amqp_transport,this.conn_established_counter=0,this.heartbeat_out=void 0,this.heartbeat_in=void 0,this.abort_idle=!1,this.socket_ready=!1,this.scheduled_reconnect=void 0,this.default_sender=void 0;for(var o in y){var p=y[o];Object.defineProperty(this,p,{get:l(p)})}Object.defineProperty(this,"error",{get:function(){return this.remote.close?this.remote.close.error:void 0}})};A.prototype=Object.create(w.prototype),A.prototype.constructor=A,A.prototype.dispatch=function(a){return o.events("Connection got event: "+a),this.listeners(a).length?(w.prototype.emit.apply(this,arguments),!0):this.container?this.container.dispatch.apply(this.container,arguments):!1},A.prototype.reset=function(){this.abort_idle&&(this.abort_idle=!1,this.local.close.error=void 0,this.state=new r,this.state.open()),this.amqp_transport=new t(this.options.id,x,n.TYPE_AMQP,this),this.sasl_transport=void 0,this.transport=this.amqp_transport,thi
 s.state.disconnected(),this.remote={},this.remote_channel_map={};for(var a in this.local_channel_map)this.local_channel_map[a].reset();this.socket_ready=!1},A.prototype.connect=function(){return this.is_server=!1,this._connect(this.options.connection_details(this.conn_established_counter)),this.open(),this},A.prototype.reconnect=function(){return this.scheduled_reconnect=void 0,o.reconnect("reconnecting..."),this.reset(),this._connect(this.options.connection_details(this.conn_established_counter)),c.nextTick(this._process.bind(this)),this},A.prototype._connect=function(a){return a.connect?this.init(a.connect(a.port,a.host,a.options,this.connected.bind(this))):this.init(j(a)(a.port,a.host,a.options,this.connected.bind(this))),this},A.prototype.accept=function(a){return this.is_server=!0,o.io("["+this.id+"] client accepted: "+f(a)),this.socket_ready=!0,this.init(a)},A.prototype.init=function(a){if(this.socket=a,this.socket.on("data",this.input.bind(this)),this.socket.on("error",this.o
 n_error.bind(this)),this.socket.on("end",this.eof.bind(this)),this.is_server){var b;this.container&&Object.getOwnPropertyNames(this.container.sasl_server_mechanisms).length&&(b=this.container.sasl_server_mechanisms),this.socket.encrypted&&this.socket.authorized&&this.get_option("enable_sasl_external",!1)&&(b=p.server_add_external(b?q.clone(b):{})),b&&(this.sasl_transport=new p.Server(this,b))}else{var c=this.get_option("sasl_mechanisms");if(!c){var d=this.get_option("username"),e=this.get_option("password");d&&(c=p.client_mechanisms(),e?c.enable_plain(d,e):c.enable_anonymous(d))}this.socket.encrypted&&this.options.cert&&this.get_option("enable_sasl_external",!1)&&(c||(c=p.client_mechanisms()),c.enable_external()),c&&(this.sasl_transport=new p.Client(this,c))}return this.transport=this.sasl_transport?this.sasl_transport:this.amqp_transport,this},A.prototype.attach_sender=function(a){return this.session_policy.get_session().attach_sender(a)},A.prototype.open_sender=A.prototype.attach_
 sender,A.prototype.attach_receiver=function(a){return this.session_policy.get_session().attach_receiver(a)},A.prototype.open_receiver=A.prototype.attach_receiver,A.prototype.get_option=function(a,b){return void 0!==this.options[a]?this.options[a]:this.container?this.container.get_option(a,b):b},A.prototype.send=function(a){return void 0===this.default_sender&&(this.default_sender=this.open_sender({target:{}})),this.default_sender.send(a)},A.prototype.connected=function(){this.socket_ready=!0,this.conn_established_counter++,o.io("["+this.options.id+"] connected "+f(this.socket)),this.output()},A.prototype.sasl_failed=function(a){this.transport_error={condition:"amqp:unauthorized-access",description:a},this._handle_error()},A.prototype._handle_error=function(){var a=this.get_error();if(a){var b=this.dispatch("connection_error",this._context());return b=this.dispatch("connection_close",this._context())||b,b||w.prototype.emit.call(this.container,"error",new e(a.description,a.condition,t
 his)),!0}return!1},A.prototype.get_error=function(){return this.transport_error?this.transport_error:this.remote.close&&this.remote.close.error?this.remote.close.error:void 0},A.prototype.output=function(){this.socket&&this.socket_ready&&(this.heartbeat_out&&clearTimeout(this.heartbeat_out),this.transport.write(this.socket),(this.is_closed()&&this.state.has_settled()||this.abort_idle||this.transport_error)&&!this.transport.has_writes_pending()?this.socket.end():this.is_open()&&this.remote.open.idle_time_out&&(this.heartbeat_out=setTimeout(this._write_frame.bind(this),this.remote.open.idle_time_out/2)))},A.prototype.input=function(a){this.heartbeat_in&&clearTimeout(this.heartbeat_in),o.io("["+this.options.id+"] read "+a.length+" bytes");var b;this.previous_input?(b=d.concat([this.previous_input,a],this.previous_input.length+a.length),this.previous_input=null):b=a;var c=this.transport.read(b,this);c<b.length&&(this.previous_input=b.slice(c)),this.local.open.idle_time_out&&(this.heartb
 eat_in=setTimeout(this.idle.bind(this),this.local.open.idle_time_out)),this.transport.has_writes_pending()?this.output():this.is_closed()&&this.state.has_settled()?this.socket.end():this.is_open()&&this.remote.open.idle_time_out&&!this.heartbeat_out&&(this.heartbeat_out=setTimeout(this._write_frame.bind(this),this.remote.open.idle_time_out/2))},A.prototype.idle=function(){this.is_open()&&(this.abort_idle=!0,this.local.close.error={condition:"amqp:resource-limit-exceeded",description:"max idle time exceeded"},this.close())},A.prototype.on_error=function(a){console.log("["+this.options.id+"] error: "+a),this._disconnected()},A.prototype.eof=function(){this._disconnected()},A.prototype._disconnected=function(){if(this.heartbeat_out&&clearTimeout(this.heartbeat_out),this.heartbeat_in&&clearTimeout(this.heartbeat_in),!this.is_closed()&&void 0===this.scheduled_reconnect&&(this.dispatch("disconnected",this._context())||console.log("["+this.options.id+"] disconnected "),!this.is_server&&!th
 is.transport_error&&this.options.reconnect)){var a=this.options.reconnect(this.conn_established_counter);a>=0&&(o.reconnect("Scheduled reconnect in "+a+"ms"),this.scheduled_reconnect=setTimeout(this.reconnect.bind(this),a))}},A.prototype.open=function(){this.state.open()&&this._register()},A.prototype.close=function(a){a&&(this.local.close.error=a),this.state.close()&&this._register()},A.prototype.is_open=function(){return this.state.is_open()},A.prototype.is_closed=function(){return this.state.is_closed()},A.prototype.create_session=function(){for(var a=0;this.local_channel_map[a];)a++;var b=new s(this,a);return this.local_channel_map[a]=b,b},A.prototype.on_open=function(a){if(!this.state.remote_opened())throw Error("Open already received");this.remote.open=a.performative,this.open(),this.dispatch("connection_open",this._context())},A.prototype.on_close=function(a){if(!this.state.remote_closed())throw Error("Close already received");this.remote.close=a.performative,this.close(),thi
 s.remote.close.error?this._handle_error():this.dispatch("connection_close",this._context()),this.heartbeat_out&&clearTimeout(this.heartbeat_out)},A.prototype._register=function(){this.registered||(this.registered=!0,c.nextTick(this._process.bind(this)))},A.prototype._process=function(){this.registered=!1;do{this.state.need_open()&&this._write_open();for(var a in this.local_channel_map)this.local_channel_map[a]._process();this.state.need_close()&&this._write_close()}while(!this.state.has_settled())},A.prototype._write_frame=function(a,b,c){this.amqp_transport.encode(n.amqp_frame(a,b,c)),this.output()},A.prototype._write_open=function(){this._write_frame(0,this.local.open.described())},A.prototype._write_close=function(){this._write_frame(0,this.local.close.described())},A.prototype.on_begin=function(a){var b;if(null===a.performative.remote_channel||void 0===a.performative.remote_channel)b=this.create_session(),b.local.begin.remote_channel=a.channel;else if(b=this.local_channel_map[a.
 performative.remote_channel],!b)throw Error("Invalid value for remote channel "+a.performative.remote_channel);b.on_begin(a),this.remote_channel_map[a.channel]=b},A.prototype.get_peer_certificate=function(){return this.socket&&this.socket.getPeerCertificate?this.socket.getPeerCertificate():void 0},A.prototype.get_tls_socket=function(){return!this.socket||"tls"!==this.options.transport&&"ssl"!==this.options.transport?void 0:this.socket},A.prototype._context=function(a){var b=a?a:{};return b.connection=this,this.container&&(b.container=this.container),b},m("end"),m("attach"),m("detach"),m("transfer"),m("disposition"),m("flow"),b.exports=A}).call(this,a("_process"),a("buffer").Buffer)},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./sasl.js":8,"./session.js":9,"./transport.js":11,"./util.js":13,_process:21,buffer:16,events:20,net:15,tls:15,util:30}],2:[function(a,b,c){"use strict";var d=function(){this.init()};d.prototype.init=function(){this.local_open=!1,this.remote_open=!1,this.o
 pen_requests=0,this.close_requests=0,this.initialised=!1},d.prototype.open=function(){return this.initialised=!0,this.local_open?!1:(this.local_open=!0,this.open_requests++,!0)},d.prototype.close=function(){return this.local_open?(this.local_open=!1,this.close_requests++,!0):!1},d.prototype.disconnected=function(){var a=this.local_open;this.init(),a?this.open():this.close()},d.prototype.remote_opened=function(){return this.remote_open?!1:(this.remote_open=!0,!0)},d.prototype.remote_closed=function(){return this.remote_open?(this.remote_open=!1,!0):!1},d.prototype.is_open=function(){return this.local_open&&this.remote_open},d.prototype.is_closed=function(){return this.initialised&&!this.local_open&&!this.remote_open},d.prototype.has_settled=function(){return 0===this.open_requests&&0===this.close_requests},d.prototype.need_open=function(){return this.open_requests>0?(this.open_requests--,!0):!1},d.prototype.need_close=function(){return this.close_requests>0?(this.close_requests--,!0)
 :!1},b.exports=d},{}],3:[function(a,b,c){"use strict";function d(a,b){var c=e.define_composite(b);f[b.name]=c.create,g[Number(c.descriptor.numeric).toString(10)]=c,g[c.descriptor.symbolic]=c}var e=a("./types.js"),f={},g={};f.read_header=function(a){var b=4,c={},d=a.toString("ascii",0,b);if("AMQP"!==d)throw Error("Invalid protocol header for AMQP "+d);if(c.protocol_id=a.readUInt8(b++),c.major=a.readUInt8(b++),c.minor=a.readUInt8(b++),c.revision=a.readUInt8(b++),1!==c.major||0!==c.minor)throw Error("Unsupported AMQP version: "+JSON.stringify(c));return c},f.write_header=function(a,b){var c=4;return a.write("AMQP",0,c,"ascii"),a.writeUInt8(b.protocol_id,c++),a.writeUInt8(b.major,c++),a.writeUInt8(b.minor,c++),a.writeUInt8(b.revision,c++),8},f.TYPE_AMQP=0,f.TYPE_SASL=1,f.read_frame=function(a){var b=new e.Reader(a),c={};if(c.size=b.read_uint(4),b.remaining<c.size)return null;var d=b.read_uint(1);if(2>d)throw Error("Invalid data offset, must be at least 2 was "+d);if(c.type=b.read_uint(1
 ),c.type===f.TYPE_AMQP)c.channel=b.read_uint(2);else{if(c.type!==f.TYPE_SASL)throw Error("Unknown frame type "+c.type);b.skip(2)}if(d>1&&b.skip(4*d-8),b.remaining()){c.performative=b.read();var h=g[c.performative.descriptor.value];h&&(c.performative=new h(c.performative.value)),b.remaining()&&(c.payload=b.read_bytes(b.remaining()))}return c},f.write_frame=function(a){var b=new e.Writer;if(b.skip(4),b.write_uint(2,1),b.write_uint(a.type,1),a.type===f.TYPE_AMQP)b.write_uint(a.channel,2);else{if(a.type!==f.TYPE_SASL)throw Error("Unknown frame type "+a.type);b.write_uint(0,2)}a.performative&&(b.write(a.performative),a.payload&&b.write_bytes(a.payload));var c=b.toBuffer();return c.writeUInt32BE(c.length,0),c},f.amqp_frame=function(a,b,c){return{channel:a||0,type:f.TYPE_AMQP,performative:b,payload:c}},f.sasl_frame=function(a){return{channel:0,type:f.TYPE_SASL,performative:a}};var h={name:"open",code:16,fields:[{name:"container_id",type:"string",mandatory:!0},{name:"hostname",type:"string"
 },{name:"max_frame_size",type:"uint",default_value:4294967295},{name:"channel_max",type:"ushort",default_value:65535},{name:"idle_time_out",type:"uint"},{name:"outgoing_locales",type:"symbol",multiple:!0},{name:"incoming_locales",type:"symbol",multiple:!0},{name:"offered_capabilities",type:"symbol",multiple:!0},{name:"desired_capabilities",type:"symbol",multiple:!0},{name:"properties",type:"symbolic_map"}]},i={name:"begin",code:17,fields:[{name:"remote_channel",type:"ushort"},{name:"next_outgoing_id",type:"uint",mandatory:!0},{name:"incoming_window",type:"uint",mandatory:!0},{name:"outgoing_window",type:"uint",mandatory:!0},{name:"handle_max",type:"uint",default_value:"4294967295"},{name:"offered_capabilities",type:"symbol",multiple:!0},{name:"desired_capabilities",type:"symbol",multiple:!0},{name:"properties",type:"symbolic_map"}]},j={name:"attach",code:18,fields:[{name:"name",type:"string",mandatory:!0},{name:"handle",type:"uint",mandatory:!0},{name:"role",type:"boolean",mandatory
 :!0},{name:"snd_settle_mode",type:"ubyte",default_value:2},{name:"rcv_settle_mode",type:"ubyte",default_value:0},{name:"source",type:"*"},{name:"target",type:"*"},{name:"unsettled",type:"map"},{name:"incomplete_unsettled",type:"boolean",default_value:!1},{name:"initial_delivery_count",type:"uint"},{name:"max_message_size",type:"ulong"},{name:"offered_capabilities",type:"symbol",multiple:!0},{name:"desired_capabilities",type:"symbol",multiple:!0},{name:"properties",type:"symbolic_map"}]},k={name:"flow",code:19,fields:[{name:"next_incoming_id",type:"uint"},{name:"incoming_window",type:"uint",mandatory:!0},{name:"next_outgoing_id",type:"uint",mandatory:!0},{name:"outgoing_window",type:"uint",mandatory:!0},{name:"handle",type:"uint"},{name:"delivery_count",type:"uint"},{name:"link_credit",type:"uint"},{name:"available",type:"uint"},{name:"drain",type:"boolean",default_value:!1},{name:"echo",type:"boolean",default_value:!1},{name:"properties",type:"symbolic_map"}]},l={name:"transfer",cod
 e:20,fields:[{name:"handle",type:"uint",mandatory:!0},{name:"delivery_id",type:"uint"},{name:"delivery_tag",type:"binary"},{name:"message_format",type:"uint"},{name:"settled",type:"boolean"},{name:"more",type:"boolean",default_value:!1},{name:"rcv_settle_mode",type:"ubyte"},{name:"state",type:"delivery_state"},{name:"resume",type:"boolean",default_value:!1},{name:"aborted",type:"boolean",default_value:!1},{name:"batchable",type:"boolean",default_value:!1}]},m={name:"disposition",code:21,fields:[{name:"role",type:"boolean",mandatory:!0},{name:"first",type:"uint",mandatory:!0},{name:"last",type:"uint"},{name:"settled",type:"boolean",default_value:!1},{name:"state",type:"*"},{name:"batchable",type:"boolean",default_value:!1}]},n={name:"detach",code:22,fields:[{name:"handle",type:"uint",mandatory:!0},{name:"closed",type:"boolean",default_value:!1},{name:"error",type:"error"}]},o={name:"end",code:23,fields:[{name:"error",type:"error"}]},p={name:"close",code:24,fields:[{name:"error",type:
 "error"}]};d(f.TYPE_AMQP,h),d(f.TYPE_AMQP,i),d(f.TYPE_AMQP,j),d(f.TYPE_AMQP,k),d(f.TYPE_AMQP,l),d(f.TYPE_AMQP,m),d(f.TYPE_AMQP,n),d(f.TYPE_AMQP,o),d(f.TYPE_AMQP,p);var q={name:"sasl_mechanisms",code:64,fields:[{name:"sasl_server_mechanisms",type:"symbol",multiple:!0,mandatory:!0}]},r={name:"sasl_init",code:65,fields:[{name:"mechanism",type:"symbol",mandatory:!0},{name:"initial_response",type:"binary"},{name:"hostname",type:"string"}]},s={name:"sasl_challenge",code:66,fields:[{name:"challenge",type:"binary",mandatory:!0}]},t={name:"sasl_response",code:67,fields:[{name:"response",type:"binary",mandatory:!0}]},u={name:"sasl_outcome",code:68,fields:[{name:"code",type:"ubyte",mandatory:!0},{name:"additional_data",type:"binary"}]};d(f.TYPE_SASL,q),d(f.TYPE_SASL,r),d(f.TYPE_SASL,s),d(f.TYPE_SASL,t),d(f.TYPE_SASL,u),b.exports=f},{"./types.js":12}],4:[function(a,b,c){(function(c){"use strict";function d(a){a.delivery.settled=!0}function e(a){a.delivery.update(void 0,k.accepted().described())
 }function f(a,b,c){Error.call(this),Error.captureStackTrace(this,this.constructor),this.message=a,this.condition=b,this.description=a,this.link=c}function g(a){switch(a){case"name":case"handle":case"role":case"initial_delivery_count":return!0;default:return!1}}function h(a){return function(){return this.remote.attach?this.remote.attach[a]:void 0}}var i=a("./frames.js"),j=a("./log.js"),k=a("./message.js"),l=a("./terminus.js"),m=a("./endpoint.js"),n=function(a){this.window=a};n.prototype.update=function(a){var b=this.window-a.receiver.credit;a.receiver.flow(b)},a("util").inherits(f,Error);var o=a("events").EventEmitter,p=Object.create(o.prototype);p.dispatch=function(a){return j.events("Link got event: "+a),o.prototype.emit.apply(this.observers,arguments),this.listeners(a).length?(o.prototype.emit.apply(this,arguments),!0):this.session.dispatch.apply(this.session,arguments)},p.set_source=function(a){this.local.attach.source=l.source(a).described()},p.set_target=function(a){this.local.
 attach.target=l.target(a).described()},p.attach=function(){this.state.open()&&this.connection._register()},p.open=p.attach,p.detach=function(){this.local.detach.closed=!1,this.state.close()&&this.connection._register()},p.close=function(a){a&&(this.local.detach.error=a),this.local.detach.closed=!0,this.state.close()&&this.connection._register()},p.is_open=function(){return this.session.is_open()&&this.state.is_open()},p.is_closed=function(){return this.session.is_closed()||this.state.is_closed()},p._process=function(){do this.state.need_open()&&this.session.output(this.local.attach.described()),this.issue_flow&&(this.session._write_flow(this),this.issue_flow=!1),this.state.need_close()&&this.session.output(this.local.detach.described());while(!this.state.has_settled())},p.on_attach=function(a){if(!this.state.remote_opened())throw Error("Attach already received");this.remote.handle||(this.remote.handle=a.handle),a.performative.source=l.unwrap(a.performative.source),a.performative.tar
 get=l.unwrap(a.performative.target),this.remote.attach=a.performative,this.open(),this.dispatch(this.is_receiver()?"receiver_open":"sender_open",this._context())},p.prefix_event=function(a){return(this.local.attach.role?"receiver_":"sender_")+a},p.on_detach=function(a){if(!this.state.remote_closed())throw Error("Detach already received");this.remote.detach=a.performative,this.close();var b=this.remote.detach.error;if(b){var c=this.dispatch(this.prefix_event("error"),this._context());c=this.dispatch(this.prefix_event("close"),this._context())||c,c||o.prototype.emit.call(this.connection.container,"error",new f(b.description,b.condition,this))}else this.dispatch(this.prefix_event("close"),this._context())};var q=["snd_settle_mode","rcv_settle_mode","source","target","max_message_size","offered_capabilities","desired_capabilities","properties"];p.init=function(a,b,c,d,e){this.session=a,this.connection=a.connection,this.name=b,this.options=void 0===d?{}:d,this.state=new m,this.issue_flow
 =!1,this.local={handle:c},this.local.attach=i.attach({handle:c,name:b,role:e});for(var f in this.local.attach)g(f)||void 0===this.options[f]||(this.local.attach[f]=this.options[f]);this.local.detach=i.detach({handle:c,closed:!0}),this.remote={handle:void 0},this.delivery_count=0,this.credit=0,this.observers=new o;for(var j in q){var k=q[j];Object.defineProperty(this,k,{get:h(k)})}Object.defineProperty(this,"error",{get:function(){return this.remote.detach?this.remote.detach.error:void 0}})},p.reset=function(){this.state.disconnected(),this.remote={handle:void 0},this.delivery_count=0,this.credit=0},p.has_credit=function(){return this.credit>0},p.is_receiver=function(){return this.local.attach.role},p._context=function(a){var b=a?a:{};return this.is_receiver()?b.receiver=this:b.sender=this,this.session._context(b)},p.get_option=function(a,b){return void 0!==this.options[a]?this.options[a]:this.session.get_option(a,b)};var r=function(a,b,c,e){this.init(a,b,c,e,!1),this._draining=!1,th
 is._drained=!1,this.local.attach.initial_delivery_count=0,this.tag=0,this.get_option("autosettle",!0)&&this.observers.on("settled",d);var f=this;this.get_option("treat_modified_as_released",!0)&&this.observers.on("modified",function(a){f.dispatch("released",a)})};r.prototype=Object.create(p),r.prototype.constructor=r,r.prototype._get_drain=function(){if(this._draining&&this._drained&&this.credit){for(;this.credit;)++this.delivery_count,--this.credit;return!0}return!1},r.prototype.set_drained=function(a){this._drained=a,this._draining&&this._drained&&(this.issue_flow=!0)},r.prototype.next_tag=function(){return new c(new String(this.tag++))},r.prototype.sendable=function(){return this.credit&&this.session.outgoing.available()},r.prototype.on_flow=function(a){var b=a.performative;this.credit=b.delivery_count+b.link_credit-this.delivery_count,this._draining=b.drain,this._drained=this.credit>0,this.is_open()&&(this.dispatch("sender_flow",this._context()),this._draining&&this.dispatch("se
 nder_draining",this._context()),this.sendable()&&this.dispatch("sendable",this._context()))},r.prototype.on_transfer=function(){throw Error("got transfer on sending link")},r.prototype.send=function(a,b){var c=this.session.send(this,b?b:this.next_tag(),k.encode(a),0);return 1===this.local.attach.snd_settle_mode&&(c.settled=!0),c};var s=function(a,b,c,d){this.init(a,b,c,d,!0),this.drain=!1,this.set_credit_window(this.get_option("credit_window",100)),this.get_option("autoaccept",!0)&&this.observers.on("message",e)};s.prototype=Object.create(p),s.prototype.constructor=s,s.prototype.on_flow=function(a){this.dispatch("receiver_flow",this._context()),a.performative.drain&&(a.performative.link_credit>0?console.log("ERROR: received flow with drain set, but non zero credit"):this.dispatch("receiver_drained",this._context()))},s.prototype.flow=function(a){a>0&&(this.credit+=a,this.issue_flow=!0,this.connection._register())},s.prototype.add_credit=s.prototype.flow,s.prototype._get_drain=functi
 on(){return this.drain},s.prototype.set_credit_window=function(a){if(a>0){var b=new n(a),c=b.update.bind(b);this.observers.on("message",c),this.observers.on("receiver_open",c)}},b.exports={Sender:r,Receiver:s}}).call(this,a("buffer").Buffer)},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./message.js":6,"./terminus.js":10,buffer:16,events:20,util:30}],5:[function(a,b,c){"use strict";var d=a("debug");b.exports={frames:d("rhea:frames"),raw:d("rhea:raw"),reconnect:d("rhea:reconnect"),events:d("rhea:events"),message:d("rhea:message"),flow:d("rhea:flow"),io:d("rhea:io")}},{debug:31}],6:[function(a,b,c){"use strict";function d(a,b,c){b.descriptor=a,p[a.symbolic]=b,p[Number(a.numeric).toString(10)]=b,c&&q.push(c)}function e(a){var b=n.define_composite(a);r[a.name]=b.create,o[Number(b.descriptor.numeric).toString(10)]=b,o[b.descriptor.symbolic]=b;var c=function(c,d){c[a.name]=new b(d.value)},e=function(c,d){d[a.name]&&(d[a.name].described?c.push(d[a.name].described()):c.push(b.create(d[a
 .name]).described()))};d(b.descriptor,c,e)}function f(a){var b={numeric:a.code};b.symbolic="amqp:"+a.name.replace(/_/g,"-")+":map";var c=function(b,c){b[a.name]=n.unwrap(c)},e=function(c,d){d[a.name]&&c.push(n.described(n.wrap_ulong(b.numeric),n.wrap_map(d[a.name])))};d(b,c,e)}function g(a){var b={};for(var c in a){var d=a[c];for(var e in d)b[d[e]]=c}return b}function h(a,b){for(var c in a){var d=a[c];"object"==typeof d?h(d,b[c]):b[c]=d}}function i(a,b,c){Object.defineProperty(a,c,{get:function(){return this[b]?this[b][c]:void 0},set:function(a){void 0===this[b]&&(this[b]={}),this[b][c]=a}})}function j(a){var b={toJSON:function(){var a={};for(var b in u){var c=u[b];if("function"!=typeof this[c]){var d=s[c];if(d)for(var e in d)void 0!==this[d[e]]&&(void 0===a[c]&&(a[c]={}),a[c][d[e]]=this[d[e]]);else this[c]&&(a[c]=this[c])}}return a},inspect:function(){return JSON.stringify(this.toJSON())},toString:function(){return JSON.stringify(this.toJSON())}};for(var c in t)i(b,t[c],c);return a
 &&h(a,b),b}function k(a){var b=void 0;for(var c in a)if("function"!=typeof a[c]&&u.indexOf(c)<0){void 0===b&&(b={});var d=t[c]||"application_properties";void 0===b[d]&&(b[d]={}),b[d][c]=a[c]}else void 0!==b&&(b[c]=a[c]);return b||a}function l(a){var b=n.define_composite(a);b.composite_type=a.name,r[a.name]=b.create,v[Number(b.descriptor.numeric).toString(10)]=b,v[b.descriptor.symbolic]=b,r["is_"+a.name]=function(b){if(b&&b.descriptor){var c=v[b.descriptor.value];if(c)return c.descriptor.numeric===a.code}return!1}}var m=a("./log.js"),n=a("./types.js"),o={},p={},q=[],r={};e({name:"header",code:112,fields:[{name:"durable",type:"boolean",default_value:!1},{name:"priority",type:"ubyte",default_value:4},{name:"ttl",type:"uint"},{name:"first_acquirer",type:"boolean",default_value:!1},{name:"delivery_count",type:"uint",default_value:0}]}),f({name:"delivery_annotations",code:113}),f({name:"message_annotations",code:114}),e({name:"properties",code:115,fields:[{name:"message_id",type:"message_
 id"},{name:"user_id",type:"binary"},{name:"to",type:"string"},{name:"subject",type:"string"},{name:"reply_to",type:"string"},{name:"correlation_id",type:"message_id"},{name:"content_type",type:"symbol"},{name:"content_encoding",type:"symbol"},{name:"absolute_expiry_time",type:"timestamp"},{name:"creation_time",type:"timestamp"},{name:"group_id",type:"string"},{name:"group_sequence",type:"uint"},{name:"reply_to_group_id",type:"string"}]}),f({name:"application_properties",code:116}),d({numeric:119,symbolic:"amqp:value:*"},function(a,b){a.body=n.unwrap(b)},function(a,b){a.push(n.described(n.wrap_ulong(119),n.wrap(b.body)))}),f({name:"footer",code:120});var s={properties:["message_id","user_id","to","subject","reply_to","correlation_id","content_type","content_encoding","absolute_expiry_time","creation_time","group_id","group_sequence","reply_to_group_id"],header:["durable","priority","ttl","first_acquirer","delivery_count"]},t=g(s),u=["header","delivery_annotations","message_annotation
 s","properties","application_properties","body","footer"];r.encode=function(a){var b=[],c=k(a);q.forEach(function(a){a(b,c)});for(var d=new n.Writer,e=0;e<b.length;e++)m.message("Encoding section "+(e+1)+" of "+b.length+": "+b[e]),d.write(b[e]);var f=d.toBuffer();return m.message("encoded "+f.length+" bytes"),f},r.decode=function(a){for(var b=j(),c=new n.Reader(a);c.remaining();){var d=c.read();if(m.message("decoding section: "+JSON.stringify(d)+" of type: "+JSON.stringify(d.descriptor)),d.descriptor){var e=p[d.descriptor.value];e?e(b,d):console.log("WARNING: did not recognise message section with descriptor "+d.descriptor)}else console.log("WARNING: expected described message section got "+JSON.stringify(d))}if(b.application_properties)for(var f in b.application_properties)i(b,"application_properties",f);return b};var v={};r.unwrap_outcome=function(a){if(a&&a.descriptor){var b=v[a.descriptor.value];if(b)return new b(n.unwrap(a))}return console.log("unrecognised outcome: "+JSON.stri
 ngify(a)),a},r.are_outcomes_equivalent=function(a,b){return void 0===a&&void 0===b?!0:void 0===a||void 0===b?!1:a.descriptor.value===b.descriptor.value&&JSON.stringify(a)===JSON.stringify(b)},l({name:"received",code:35,fields:[{name:"section_number",type:"uint",mandatory:!0},{name:"section_offset",type:"ulong",mandatory:!0}]}),l({name:"accepted",code:36,fields:[]}),l({name:"rejected",code:37,fields:[{name:"error",type:"error"}]}),l({name:"released",code:38,fields:[]}),l({name:"modified",code:39,fields:[{name:"delivery_failed",type:"boolean"},{name:"undeliverable_here",type:"boolean"},{name:"message_annotations",type:"map"}]}),b.exports=r},{"./log.js":5,"./types.js":12}],7:[function(a,b,c){"use strict";function d(a,b){return b?Array.isArray(b)?b.indexOf(a)>-1:a===b:!1}var e=a("url"),f={counter:1,next:function(){return this.counter++}},g=function(a,b){var c=e.parse(b);this.connection=a.connect({host:c.hostname,port:c.port}),this.connection.on("message",this._response.bind(this)),this.
 connection.on("receiver_open",this._ready.bind(this)),this.sender=this.connection.attach_sender(c.path.substr(1)),this.receiver=this.connection.attach_receiver({source:{dynamic:!0}}),this.id_generator=f,this.pending=[],this.outstanding={}};g.prototype._request=function(a,b,c,d){var e={properties:{}};e.properties.subject=b,e.body=c,e.properties.message_id=a,e.properties.reply_to=this.receiver.remote.attach.source.address,this.outstanding[a]=d,this.sender.send(e)},g.prototype._response=function(a){var b=a.message.properties.correlation_id,c=this.outstanding[b];c?"ok"===a.message.properties.subject?c(a.message.body):c(void 0,{name:a.message.properties.subject,description:a.message.body}):console.log("no request pending for "+b+", ignoring response")},g.prototype._ready=function(){this._process_pending()},g.prototype._process_pending=function(){for(var a=0;a<this.pending.length;a++){var b=this.pending[a];this._request(b.id,b.name,b.args,b.callback)}this.pending=[]},g.prototype.call=func
 tion(a,b,c){var d=this.id_generator.next();this.receiver.is_open()&&0===this.pending.length?this._request(d,a,b,c):this.pending.push({
+name:a,args:b,callback:c,id:d})},g.prototype.close=function(){this.receiver.close(),this.sender.close(),this.connection.close()},g.prototype.define=function(a){this[a]=function(b,c){this.call(a,b,c)}};var h=function(a,b){this.ttl=a,this.purged=b,this.entries={},this.timeout=void 0};h.prototype.clear=function(){this.timeout&&clearTimeout(this.timeout),this.entries={}},h.prototype.put=function(a,b){this.entries[a]={value:b,last_accessed:Date.now()},this.timeout||(this.timeout=setTimeout(this.purge.bind(this),this.ttl))},h.prototype.get=function(a){var b=this.entries[a];return b?(b.last_accessed=Date.now(),b.value):void 0},h.prototype.purge=function(){var a=Date.now(),b=[],c=0;for(var d in this.entries)a-this.entries[d].last_accessed>=this.ttl?b.push(d):c++;for(var e=0;e<b.length;e++){var f=this.entries[b[e]];delete this.entries[b[e]],this.purged(f.value)}c&&!this.timeout&&(this.timeout=setTimeout(this.purge.bind(this),this.ttl))};var i=function(a,b){this.factory=a,this.cache=new h(b,f
 unction(a){a.close()})};i.prototype.clear=function(){this.cache.clear()},i.prototype.get=function(a){var b=this.cache.get(a);return void 0===b&&(b=this.factory(a),this.cache.put(a,b)),b};var j=function(a,b,c){this.options=c||{};var d=e.parse(b);this.connection=a.connect({host:d.hostname,port:d.port}),this.connection.on("connection_open",this._connection_open.bind(this)),this.connection.on("message",this._request.bind(this)),this.receiver=this.connection.attach_receiver(d.path.substr(1)),this.callbacks={},this._send=void 0,this._clear=void 0};j.prototype._connection_open=function(){if(d("ANONYMOUS-RELAY",this.connection.remote.open.offered_capabilities)){var a=this.connection.attach_sender({target:{}});this._send=function(b){a.send(b)}}else{var b=new i(this.connection.attach_sender.bind(this.connection),this.options.cache_ttl||6e4);this._send=function(a){var c=b.get(a.properties.to);c&&c.send(a)},this._clear=function(){b.clear()}}},j.prototype._respond=function(a){var b=this;return f
 unction(c,d){d?(a.properties.subject=d.name||"error",a.body=d.description||d):(a.properties.subject="ok",a.body=c),b._send(a)}},j.prototype._request=function(a){var b=a.message,c={properties:{}};c.properties.to=b.properties.reply_to,c.properties.correlation_id=b.properties.message_id;var d=this.callbacks[b.properties.subject];d?d(b.body,this._respond(c)):(c.properties.subject="bad-method",c.body="Unrecognised method "+b.properties.subject,this._send(c))},j.prototype.bind_sync=function(a,b){this.callbacks[b||a.name]=function(b,c){var d=a(b);c(d)}},j.prototype.bind=function(a,b){this.callbacks[b||a.name]=a},j.prototype.close=function(){this._clear&&this._clear(),this.receiver.close(),this.connection.close()},b.exports={server:function(a,b,c){return new j(a,b,c)},client:function(a,b){return new g(a,b)}}},{url:26}],8:[function(a,b,c){(function(c){"use strict";function d(a){for(var b=[],c=0,d=0;d<a.length;)0===a[d]?(d>c?b.push(a.toString("utf8",c,d)):b.push(null),c=++d):++d;return d>c?b.
 push(a.toString("utf8",c,d)):b.push(null),b}var e=a("./frames.js"),f=a("./transport.js"),g={OK:0,AUTH:1,SYS:2,SYS_PERM:3,SYS_TEMP:4},h=3,i=function(a){this.callback=a,this.outcome=void 0,this.username=void 0};i.prototype.start=function(a){var b=d(a);3!==b.length&&this.connection.sasl_failed("Unexpected response in PLAIN, got "+b.length+" fields, expected 3"),this.callback(b[1],b[2])?(this.outcome=!0,this.username=b[1]):this.outcome=!1};var j=function(a,b){this.username=a,this.password=b};j.prototype.start=function(){var a=new c(1+this.username.length+1+this.password.length);return a.writeUInt8(0,0),a.write(this.username,1),a.writeUInt8(0,1+this.username.length),a.write(this.password,1+this.username.length+1),a};var k=function(){this.outcome=void 0,this.username=void 0};k.prototype.start=function(a){this.outcome=!0,this.username=a?a.toString("utf8"):"anonymous"};var l=function(a){this.username=a?a:"anonymous"};l.prototype.start=function(){var a=new c(1+this.username.length);return a.
 writeUInt8(0,0),a.write(this.username,1),a};var m=function(){this.outcome=void 0,this.username=void 0};m.prototype.start=function(){this.outcome=!0};var n=function(){this.username=void 0};n.prototype.start=function(){return null};var o=function(a,b){this.connection=a,this.transport=new f(a.amqp_transport.identifier,h,e.TYPE_SASL,this),this.next=a.amqp_transport,this.mechanisms=b,this.mechanism=void 0,this.outcome=void 0,this.username=void 0;var c=Object.getOwnPropertyNames(b);this.transport.encode(e.sasl_frame(e.sasl_mechanisms({sasl_server_mechanisms:c}).described()))};o.prototype.do_step=function(a){void 0===this.mechanism.outcome?this.transport.encode(e.sasl_frame(e.sasl_challenge({challenge:a}).described())):(this.outcome=this.mechanism.outcome?g.OK:g.AUTH,this.transport.encode(e.sasl_frame(e.sasl_outcome({code:this.outcome}).described())),this.outcome===g.OK&&(this.username=this.mechanism.username,this.transport.write_complete=!0,this.transport.read_complete=!0))},o.prototype.o
 n_sasl_init=function(a){var b=this.mechanisms[a.performative.mechanism];if(b){this.mechanism=b();var c=this.mechanism.start(a.performative.initial_response);this.do_step(c)}else this.outcome=g.AUTH,this.transport.encode(e.sasl_frame(e.sasl_outcome({code:this.outcome}).described()))},o.prototype.on_sasl_response=function(a){this.do_step(this.mechanism.step(a.performative.response))},o.prototype.has_writes_pending=function(){return this.transport.has_writes_pending()||this.next.has_writes_pending()},o.prototype.write=function(a){return this.transport.write_complete&&0===this.transport.pending.length?this.next.write(a):this.transport.write(a)},o.prototype.read=function(a){return this.transport.read_complete?this.next.read(a):this.transport.read(a)};var p=function(a,b){this.connection=a,this.transport=new f(a.amqp_transport.identifier,h,e.TYPE_SASL,this),this.next=a.amqp_transport,this.mechanisms=b,this.mechanism=void 0,this.mechanism_name=void 0,this.failed=!1};p.prototype.on_sasl_mech
 anisms=function(a){for(var b=0;void 0===this.mechanism&&b<a.performative.sasl_server_mechanisms.length;b++){var c=a.performative.sasl_server_mechanisms[b],d=this.mechanisms[c];d&&(this.mechanism=d(),this.mechanism_name=c)}if(this.mechanism){var f=this.mechanism.start();this.transport.encode(e.sasl_frame(e.sasl_init({mechanism:this.mechanism_name,initial_response:f}).described()))}else this.failed=!0,this.connection.sasl_failed("No suitable mechanism; server supports "+a.performative.sasl_server_mechanisms)},p.prototype.on_sasl_challenge=function(a){var b=this.mechanism.step(a.performative.challenge);this.transport.encode(e.sasl_frame(e.sasl_response({response:b}).described()))},p.prototype.on_sasl_outcome=function(a){switch(a.performative.code){case g.OK:this.transport.read_complete=!0,this.transport.write_complete=!0;break;default:this.transport.write_complete=!0,this.connection.sasl_failed("Failed to authenticate: "+a.performative.code)}},p.prototype.has_writes_pending=function(){
 return this.transport.has_writes_pending()||this.next.has_writes_pending()},p.prototype.write=function(a){return this.transport.write_complete?this.next.write(a):this.transport.write(a)},p.prototype.read=function(a){return this.transport.read_complete?this.next.read(a):this.transport.read(a)};var q={enable_anonymous:function(){this.ANONYMOUS=function(){return new k}},enable_plain:function(a){this.PLAIN=function(){return new i(a)}}},r={enable_anonymous:function(a){this.ANONYMOUS=function(){return new l(a)}},enable_plain:function(a,b){this.PLAIN=function(){return new j(a,b)}},enable_external:function(){this.EXTERNAL=function(){return new n}}};b.exports={Client:p,Server:o,server_mechanisms:function(){return Object.create(q)},client_mechanisms:function(){return Object.create(r)},server_add_external:function(a){return a.EXTERNAL=function(){return new m},a}}}).call(this,a("buffer").Buffer)},{"./frames.js":3,"./transport.js":11,buffer:16}],9:[function(a,b,c){(function(c){"use strict";funct
 ion d(a){var b,c,d,e,g;for(e=0;e<a.length;e++)g=a[e],void 0===b&&(b=g,c=g,d=g.id),i.are_outcomes_equivalent(c.state,g.state)&&c.settled===g.settled&&d===g.id?(c.id!==g.id&&(c=g),d++):(b.link.session.output(f.disposition({role:b.link.is_receiver(),first:b.id,last:c.id,state:b.state,settled:b.settled}).described()),b=g,c=g,d=g.id);void 0!==b&&void 0!==c&&b.link.session.output(f.disposition({role:b.link.is_receiver(),first:b.id,last:c.id,state:b.state,settled:b.settled}).described())}function e(a,b,c){var d=b?b:{};"string"==typeof b&&(d={},d[c]=b),d.name||(d.name=k.generate_uuid());var e=a(d.name,d);for(var f in{source:0,target:0})d[f]&&("string"==typeof d[f]&&(d[f]={address:d[f]}),e["set_"+f](d[f]));return e.attach(),e}var f=a("./frames.js"),g=a("./link.js"),h=a("./log.js"),i=a("./message.js"),j=a("./types.js"),k=a("./util.js"),l=a("./endpoint.js"),m=a("events").EventEmitter,n=function(a){this.capacity=a,this.size=0,this.head=0,this.tail=0,this.entries=[]};n.prototype.available=functi
 on(){return this.capacity-this.size},n.prototype.push=function(a){if(!(this.size<this.capacity))throw Error("circular buffer overflow: head="+this.head+" tail="+this.tail+" size="+this.size+" capacity="+this.capacity);this.entries[this.tail]=a,this.tail=(this.tail+1)%this.capacity,this.size++},n.prototype.pop_if=function(a){for(var b=0;this.size&&a(this.entries[this.head]);)this.entries[this.head]=void 0,this.head=(this.head+1)%this.capacity,this.size--,b++;return b},n.prototype.by_id=function(a){if(this.size>0){var b=a-this.entries[this.head].id;if(b<this.size)return this.entries[(this.head+b)%this.capacity]}return void 0},n.prototype.get_head=function(){return this.size>0?this.entries[this.head]:void 0};var o=function(){this.deliveries=new n(2048),this.updated=[],this.pending_dispositions=[],this.next_delivery_id=0,this.next_pending_delivery=0,this.next_transfer_id=0,this.window=j.MAX_UINT,this.remote_next_transfer_id=void 0,this.remote_window=void 0};o.prototype.available=functio
 n(){return this.deliveries.available()},o.prototype.send=function(a,b,c,d){var e={id:this.next_delivery_id++,tag:b,link:a,data:c,format:d?d:0,sent:!1,settled:!1,state:void 0,remote_settled:!1,remote_state:void 0},f=this;return e.update=function(a,b){f.update(e,a,b)},this.deliveries.push(e),e},o.prototype.on_begin=function(a){this.remote_window=a.incoming_window},o.prototype.on_flow=function(a){this.remote_next_transfer_id=a.next_incoming_id,this.remote_window=a.incoming_window},o.prototype.on_disposition=function(a){for(var b=a.last?a.last:a.first,c=a.first;b>=c;c++){var d=this.deliveries.by_id(c);if(d&&!d.remote_settled){var e=!1;a.settled&&(d.remote_settled=a.settled,e=!0),a.state&&a.state!==d.remote_state&&(d.remote_state=i.unwrap_outcome(a.state),e=!0),e&&this.updated.push(d)}}},o.prototype.update=function(a,b,c){a&&(a.settled=b,void 0!==c&&(a.state=c),a.remote_settled||this.pending_dispositions.push(a),a.link.connection._register())},o.prototype.transfer_window=function(){retur
 n this.remote_window?this.remote_window-(this.next_transfer_id-this.remote_next_transfer_id):0},o.prototype.process=function(){for(var a;this.next_pending_delivery<this.next_delivery_id;){if(a=this.deliveries.by_id(this.next_pending_delivery),!a){console.log("ERROR: Next pending delivery not found: "+this.next_pending_delivery);break}if(!a.link.has_credit()){h.flow("Link has no credit");break}if(a.link.delivery_count++,a.transfers_required=1,!(this.transfer_window()>=a.transfers_required)){h.flow("Incoming window of peer preventing sending further transfers: remote_window="+this.remote_window+", remote_next_transfer_id="+this.remote_next_transfer_id+", next_transfer_id="+this.next_transfer_id);break}this.next_transfer_id+=a.transfers_required,this.window-=a.transfers_required,a.link.session.output(f.transfer({handle:a.link.local.handle,message_format:a.format,delivery_id:a.id,delivery_tag:a.tag,settled:a.settled}).described(),a.data),a.link.credit--,this.next_pending_delivery++}for(
 var b=0;b<this.updated.length;b++)a=this.updated[b],a.remote_state&&a.remote_state.constructor.composite_type&&a.link.dispatch(a.remote_state.constructor.composite_type,a.link._context({delivery:a})),a.remote_settled&&a.link.dispatch("settled",a.link._context({delivery:a}));this.updated=[],this.pending_dispositions.length&&(d(this.pending_dispositions),this.pending_dispositions=[]),this.deliveries.pop_if(function(a){return a.settled&&a.remote_settled})};var p=function(){this.deliveries=new n(2048),this.updated=[],this.next_transfer_id=0,this.next_delivery_id=void 0,this.window=2048,this.remote_next_transfer_id=void 0,this.remote_window=void 0};p.prototype.update=function(a,b,c){a&&(a.settled=b,void 0!==c&&(a.state=c),a.remote_settled||this.updated.push(a),a.link.connection._register())},p.prototype.on_transfer=function(a,b){if(this.next_transfer_id++,b.is_open()){void 0===this.next_delivery_id&&(this.next_delivery_id=a.performative.delivery_id);var d,e,f=this.deliveries.get_head();i
 f(f&&f.incomplete){if(void 0!==a.performative.delivery_id&&this.next_delivery_id!==a.performative.delivery_id)throw Error("frame sequence error: delivery "+this.next_delivery_id+" not complete, got "+a.performative.delivery_id);d=f,e=c.concat([d.data,a.payload],d.data.size()+a.payload.size())}else{if(this.next_delivery_id!==a.performative.delivery_id)throw Error("frame sequence error: expected "+this.next_delivery_id+", got "+a.performative.delivery_id);d={id:a.performative.delivery_id,tag:a.performative.delivery_tag,link:b,settled:!1,state:void 0,remote_settled:void 0===a.performative.settled?!1:a.performative.settled,remote_state:a.performative.state};var g=this;d.update=function(a,c){var e=a;void 0===e&&(e=1!==b.local.attach.rcv_settle_mode),g.update(d,e,c)},d.accept=function(){this.update(void 0,i.accepted().described())},d.release=function(a){a?this.update(void 0,i.modified(a).described()):this.update(void 0,i.released().described())},d.reject=function(a){this.update(!0,i.rejec
 ted({error:a}).described())},d.modified=function(a){this.update(!0,i.modified(a).described())},this.deliveries.push(d),e=a.payload}d.incomplete=a.performative.more,d.incomplete?d.data=e:(b.credit--,b.delivery_count++,this.next_delivery_id++,b.dispatch("message",b._context({message:i.decode(e),delivery:d})))}},p.prototype.process=function(){this.updated.length>0&&(d(this.updated),this.updated=[]),this.deliveries.pop_if(function(a){return a.settled})},p.prototype.on_begin=function(a){this.remote_window=a.outgoing_window},p.prototype.on_flow=function(a){this.remote_next_transfer_id=a.next_outgoing_id,this.remote_window=a.outgoing_window},p.prototype.on_disposition=function(a){for(var b=a.last?a.last:a.first,c=a.first;b>=c;c++){var d=this.deliveries.by_id(c);d&&!d.remote_settled&&a.settled&&(d.remote_settled=a.settled,d.link.dispatch("settled",d.link._context({delivery:d})))}};var q=function(a,b){this.connection=a,this.outgoing=new o,this.incoming=new p,this.state=new l,this.local={chan
 nel:b,handles:{}},this.local.begin=f.begin({next_outgoing_id:this.outgoing.next_transfer_id,incoming_window:this.incoming.window,outgoing_window:this.outgoing.window}),this.local.end=f.end(),this.remote={handles:{}},this.links={},this.options={}};q.prototype=Object.create(m.prototype),q.prototype.constructor=q,q.prototype.reset=function(){this.state.disconnected(),this.outgoing=new o,this.incoming=new p,this.remote={handles:{}};for(var a in this.links)this.links[a].reset()},q.prototype.dispatch=function(a){return h.events("Session got event: "+a),this.listeners(a).length?(m.prototype.emit.apply(this,arguments),!0):this.connection.dispatch.apply(this.connection,arguments)},q.prototype.output=function(a,b){this.connection._write_frame(this.local.channel,a,b)},q.prototype.create_sender=function(a,b){return this.create_link(a,g.Sender,b)},q.prototype.create_receiver=function(a,b){return this.create_link(a,g.Receiver,b)},q.prototype.get_option=function(a,b){return void 0!==this.options[a
 ]?this.options[a]:this.connection.get_option(a,b)},q.prototype.attach_sender=function(a){return e(this.create_sender.bind(this),a,"target")},q.prototype.open_sender=q.prototype.attach_sender,q.prototype.attach_receiver=function(a){return e(this.create_receiver.bind(this),a,"source")},q.prototype.open_receiver=q.prototype.attach_receiver,q.prototype.create_link=function(a,b,c){for(var d=0;this.local.handles[d];)d++;var e=new b(this,a,d,c);return this.links[a]=e,this.local.handles[d]=e,e},q.prototype.begin=function(){this.state.open()&&this.connection._register()},q.prototype.open=q.prototype.begin,q.prototype.end=function(a){a&&(this.local.end.error=a),this.state.close()&&this.connection._register()},q.prototype.close=q.prototype.end,q.prototype.is_open=function(){return this.connection.is_open()&&this.state.is_open()},q.prototype.is_closed=function(){return this.connection.is_closed()||this.state.is_closed()},q.prototype._process=function(){do{this.state.need_open()&&this.output(thi
 s.local.begin.described()),this.outgoing.process(),this.incoming.process();for(var a in this.links)this.links[a]._process();this.state.need_close()&&this.output(this.local.end.described())}while(!this.state.has_settled())},q.prototype.send=function(a,b,c,d){var e=this.outgoing.send(a,b,c,d);return this.connection._register(),e},q.prototype._write_flow=function(a){var b={next_incoming_id:this.incoming.next_transfer_id,incoming_window:this.incoming.window,next_outgoing_id:this.outgoing.next_transfer_id,outgoing_window:this.outgoing.window};a&&(a._get_drain()&&(b.drain=!0),b.delivery_count=a.delivery_count,b.handle=a.local.handle,b.link_credit=a.credit),this.output(f.flow(b).described())},q.prototype.on_begin=function(a){if(!this.state.remote_opened())throw Error("Begin already received");this.remote.channel||(this.remote.channel=a.channel),this.remote.begin=a.performative,this.outgoing.on_begin(a.performative),this.incoming.on_begin(a.performative),this.open(),this.dispatch("session_o
 pen",this._context())},q.prototype.on_end=function(a){if(!this.state.remote_closed())throw Error("End already received");this.remote.end=a.performative,this.close(),this.dispatch("session_close",this._context())},q.prototype.on_attach=function(a){var b=a.performative.name,c=this.links[b];c||(c=a.performative.role?this.create_sender(b):this.create_receiver(b)),this.remote.handles[a.performative.handle]=c,c.on_attach(a),c.remote.attach=a.performative},q.prototype.on_disposition=function(a){a.performative.role?(h.events("Received disposition for outgoing transfers"),this.outgoing.on_disposition(a.performative)):(h.events("Received disposition for incoming transfers"),this.incoming.on_disposition(a.performative)),this.connection._register()},q.prototype.on_flow=function(a){this.outgoing.on_flow(a.performative),this.incoming.on_flow(a.performative),void 0!==a.performative.handle&&this._get_link(a).on_flow(a),this.connection._register()},q.prototype._context=function(a){var b=a?a:{};retur
 n b.session=this,this.connection._context(b)},q.prototype._get_link=function(a){var b=a.performative.handle,c=this.remote.handles[b];if(!c)throw Error("Invalid handle "+b);return c},q.prototype.on_detach=function(a){this._get_link(a).on_detach(a);var b=a.performative.handle,c=this.remote.handles[b];delete this.remote.handles[b],delete this.local.handles[c.local.handle],delete this.links[c.name]},q.prototype.on_transfer=function(a){this.incoming.on_transfer(a,this._get_link(a))},b.exports=q}).call(this,a("buffer").Buffer)},{"./endpoint.js":2,"./frames.js":3,"./link.js":4,"./log.js":5,"./message.js":6,"./types.js":12,"./util.js":13,buffer:16,events:20}],10:[function(a,b,c){"use strict";function d(a){var b=e.define_composite(a);f[a.name]=b.create,g[Number(b.descriptor.numeric).toString(10)]=b,g[b.descriptor.symbolic]=b}var e=a("./types.js"),f={},g={};f.unwrap=function(a){if(a&&a.descriptor){var b=g[a.descriptor.value];if(b)return new b(a.value);console.log("Unknown terminus: "+a.descri
 ptor)}return null},d({name:"source",code:40,fields:[{name:"address",type:"string"},{name:"durable",type:"uint",default_value:0},{name:"expiry_policy",type:"symbol",default_value:"session-end"},{name:"timeout",type:"uint",default_value:0},{name:"dynamic",type:"boolean",default_value:!1},{name:"dynamic_node_properties",type:"symbolic_map"},{name:"distribution_mode",type:"symbol"},{name:"filter",type:"symbolic_map"},{name:"default_outcome",type:"*"},{name:"outcomes",type:"symbol",multiple:!0},{name:"capabilities",type:"symbol",multiple:!0}]}),d({name:"target",code:41,fields:[{name:"address",type:"string"},{name:"durable",type:"uint",default_value:0},{name:"expiry_policy",type:"symbol",default_value:"session-end"},{name:"timeout",type:"uint",default_value:0},{name:"dynamic",type:"boolean",default_value:!1},{name:"dynamic_node_properties",type:"symbolic_map"},{name:"capabilities",type:"symbol",multiple:!0}]}),b.exports=f},{"./types.js":12}],11:[function(a,b,c){(function(c){"use strict";v
 ar d=a("./frames.js"),e=a("./log.js"),f=function(a,b,c,d){this.identifier=a,this.protocol_id=b,this.frame_type=c,this.handler=d,this.pending=[],this.header_sent=void 0,this.header_received=void 0,this.write_complete=!1,this.read_complete=!1};f.prototype.has_writes_pending=function(){return this.pending.length>0},f.prototype.encode=function(a){var b=d.write_frame(a);e.frames("["+this.identifier+"] PENDING: "+JSON.stringify(a)),this.pending.push(b)},f.prototype.write=function(a){if(!this.header_sent){var b=new c(8),f={protocol_id:this.protocol_id,major:1,minor:0,revision:0};d.write_header(b,f),a.write(b),this.header_sent=f}for(var g=0;g<this.pending.length;g++)a.write(this.pending[g]),e.raw("["+this.identifier+"] SENT: "+JSON.stringify(this.pending[g]));this.pending=[]},f.prototype.read=function(a){var b=0;if(!this.header_received){if(a.length<8)return b;if(this.header_received=d.read_header(a),e.frames("["+this.identifier+"] RECV: "+JSON.stringify(this.header_received)),this.header_r
 eceived.protocol_id!==this.protocol_id)throw Error("Invalid AMQP protocol id "+this.header_received.protocol_id+" expecting: "+this.protocol_id);b=8}for(;b<a.length&&!this.read_complete;){var c=a.readUInt32BE(b);if(e.io("["+this.identifier+"] got frame of size "+c),a.length<b+c){e.io("["+this.identifier+"] incomplete frame; have only "+(a.length-b)+" of "+c);break}var f=d.read_frame(a.slice(b,b+c));if(e.frames("["+this.identifier+"] RECV: "+JSON.stringify(f)),f.type!==this.frame_type)throw Error("Invalid frame type: "+f.type);b+=c,f.performative&&f.performative.dispatch(this.handler,f)}return b},b.exports=f}).call(this,a("buffer").Buffer)},{"./frames.js":3,"./log.js":5,buffer:16}],12:[function(a,b,c){(function(a){"use strict";function c(a,b){this.type=a,this.value=b}function d(a){return Number(a).toString(16)}function e(a,b,e,f){var g,h=b>>>4;switch(g=4===h?function(){this.type=g,this.value=f}:14===h||15===h?function(a,b,c){this.type=g,this.value=a,this.array_constructor={typecode:b
 },c&&(this.array_constructor.descriptor=c)}:function(a){this.type=g,this.value=a},g.typecode=b,g.prototype=Object.create(c.prototype),g.toString=function(){return a+"#"+d(b)},h){case 4:g.width=0,g.category="fixed";break;case 5:g.width=1,g.category="fixed";break;case 6:g.width=2,g.category="fixed";break;case 7:g.width=4,g.category="fixed";break;case 8:g.width=8,g.category="fixed";break;case 9:g.width=16,g.category="fixed";break;case 10:g.width=1,g.category="variable";break;case 11:g.width=4,g.category="variable";break;case 12:g.width=1,g.category="compound";break;case 13:g.width=4,g.category="compound";break;case 14:g.width=1,g.category="array";break;case 15:g.width=4,g.category="array"}if(e)for(var i in e)g[i]=e[i];return t.by_code[g.typecode]=g,t[a]=g,g}function f(a){return{read:function(b,c){return b["read"+a](c)},write:function(b,c,d){b["write"+a](c,d)}}}function g(a){return{read:function(b,c){return b["read"+a+"BE"](c)},write:function(b,c,d){b["write"+a+"BE"](c,d)}}}function h(a
 ,b,c){if("number"==typeof b||b instanceof Number){var d=Math.floor(b/u),e=b%u;a.writeUInt32BE(d,c),a.writeUInt32BE(e,c+4)}else b.copy(a,c)}function i(a,b){var c=a.readUInt32BE(b),d=a.readUInt32BE(b+4);return 2097153>c?c*u+d:a.slice(b,b+8)}function j(a,b,c){if("number"==typeof b||b instanceof Number){var d=Math.abs(b),e=Math.floor(d/u),f=d%u;if(a.writeInt32BE(e,c),a.writeUInt32BE(f,c+4),0>b)for(var g=1,h=0;8>h;h++){var i=c+(7-h),j=(255^a[i])+g;a[i]=255&j,g=j>>8}}else b.copy(a,c)}function k(a,b){var c=a.readInt32BE(b),d=a.readUInt32BE(b+4);return 2097153>c&&c>-2097153?c*u+d:a.slice(b,b+8)}function l(a,b){for(var c=0;c<b.length;c++)if(a.type.typecode===b[c].typecode)return!0;return!1}function m(a){for(var b={},c=0;c+1<a.length;)b[a[c++]]=a[c++];return b}function n(a){var b=t.by_code[a];if(!b)throw Error("Unrecognised typecode: "+d(a));return b}function o(a,b){return a>b?a:b}function p(a){if("symbol"===a)return{typecode:t.Sym8.typecode};throw Error("TODO: Array of type "+a+" not yet sup
 ported")}function q(a,b){if(void 0!==b&&null!==b){if(Array.isArray(b)){if(!a.multiple)throw Error("Field "+a.name+" does not support multiple values, got "+JSON.stringify(b));var c=p(a.type);return t.wrap_array(b,c.typecode,c.descriptor)}if("*"===a.type)return b;var d=t["wrap_"+a.type];if(d)return d(b);throw Error("No wrapper for field "+a.name+" of type "+a.type)}if(a.mandatory)throw Error("Field "+a.name+" is mandatory");return new t.Null}function r(a,b){var c=function(){return"*"===b.type?this.value[a]:t.unwrap(this.value[a])},d=function(c){this.value[a]=q(b,c)};return{get:c,set:d,enumerable:!0,configurable:!1}}function s(a){var b=t.define_composite(a);t["wrap_"+a.name]=function(a){return b.create(a).described()},w[Number(b.descriptor.numeric).toString(10)]=b,w[b.descriptor.symbolic]=b}c.prototype.toString=function(){return this.value?this.value.toString():null},c.prototype.toLocaleString=function(){return this.value?this.value.toLocaleString():null},c.prototype.valueOf=function(
 ){return this.value},c.prototype.toJSON=function(){return this.value&&this.value.toJSON?this.value.toJSON():this.value};var t={by_code:{}};Object.defineProperty(t,"MAX_UINT",{value:4294967295,writable:!1,configurable:!1}),Object.defineProperty(t,"MAX_USHORT",{value:65535,writable:!1,configurable:!1});var u=4294967296,v=-2147483647;e("Null",64,void 0,null),e("Boolean",86,f("UInt8")),e("True",65,void 0,!0),e("False",66,void 0,!1),e("Ubyte",80,f("UInt8")),e("Ushort",96,f("UInt16BE")),e("Uint",112,f("UInt32BE")),e("SmallUint",82,f("UInt8")),e("Uint0",67,void 0,0),e("Ulong",128,{write:h,read:i}),e("SmallUlong",83,f("UInt8")),e("Ulong0",68,void 0,0),e("Byte",81,f("Int8")),e("Short",97,f("Int16BE")),e("Int",113,f("Int32BE")),e("SmallInt",84,f("Int8")),e("Long",129,{write:j,read:k}),e("SmallLong",85,f("Int8")),e("Float",114,g("Float")),e("Double",130,g("Double")),e("Decimal32",116),e("Decimal64",132),e("Decimal128",148),e("CharUTF32",115),e("Timestamp",131,{write:j,read:k}),e("Uuid",152),e(
 "Vbin8",160),e("Vbin32",176),e("Str8",161,{encoding:"utf8"}),e("Str32",177,{encoding:"utf8"}),e("Sym8",163,{encoding:"ascii"}),e("Sym32",179,{encoding:"ascii"}),e("List0",69,void 0,[]),e("List8",192),e("List32",208),e("Map8",193),e("Map32",209),e("Array8",224),e("Array32",240),t.is_ulong=function(a){return l(a,[t.Ulong,t.Ulong0,t.SmallUlong])},t.is_string=function(a){return l(a,[t.Str8,t.Str32])},t.is_symbol=function(a){return l(a,[t.Sym8,t.Sym32])},t.is_list=function(a){return l(a,[t.List0,t.List8,t.List32])},t.is_map=function(a){return l(a,[t.Map8,t.Map32])},t.wrap_boolean=function(a){return a?new t.True:new t.False},t.wrap_ulong=function(a){return 0===a?new t.Ulong0:a>255?new t.Ulong(a):new t.SmallUlong(a)},t.wrap_uint=function(a){return 0===a?new t.Uint0:a>255?new t.Uint(a):new t.SmallUint(a)},t.wrap_ushort=function(a){return new t.Ushort(a)},t.wrap_ubyte=function(a){return new t.Ubyte(a)},t.wrap_long=function(a){return a>127||-128>a?new t.Long(a):new t.SmallLong(a)},t.wrap_int=
 function(a){return a>127||-128>a?new t.Int(a):new t.SmallInt(a)},t.wrap_short=function(a){return new t.Short(a)},t.wrap_byte=function(a){return new t.Byte(a)},t.wrap_float=function(a){return new t.Float(a)},t.wrap_double=function(a){return new t.Double(a)},t.wrap_timestamp=function(a){return new t.Timestamp(a)},t.wrap_binary=function(a){return a.length>255?new t.Vbin32(a):new t.Vbin8(a)},t.wrap_string=function(a){return a.length>255?new t.Str32(a):new t.Str8(a)},t.wrap_symbol=function(a){return a.length>255?new t.Sym32(a):new t.Sym8(a)},t.wrap_list=function(a){if(0===a.length)return new t.List0;var b=a.map(t.wrap);return new t.List32(b)},t.wrap_map=function(a,b){var c=[];for(var d in a)c.push(b?b(d):t.wrap(d)),c.push(t.wrap(a[d]));return new t.Map32(c)},t.wrap_symbolic_map=function(a){return t.wrap_map(a,t.wrap_symbol)},t.wrap_array=function(a,b,c){if(b)return new t.Array32(a,b,c);throw Error("An array must specify a type for its elements")},t.wrap=function(a){var b=typeof a;if("str
 ing"===b)return t.wrap_string(a);if("boolean"===b)return a?new t.True:new t.False;if("number"===b||a instanceof Number){if(isNaN(a))throw Error("Cannot wrap NaN! "+a);return Math.floor(a)-a!==0?new t.Double(a):a>0?u>a?t.wrap_uint(a):t.wrap_ulong(a):a>v?t.wrap_int(a):t.wrap_long(a)}return a instanceof Date?t.wrap_timestamp(a.getTime()):a instanceof c?a:"undefined"===b||null===a?new t.Null:Array.isArray(a)?t.wrap_list(a):t.wrap_map(a)},t.wrap_described=function(a,b){var c=t.wrap(a);return b&&("string"==typeof b?c=t.described(t.wrap_string(b),c):("number"==typeof b||b instanceof Number)&&(c=t.described(t.wrap_ulong(b),c))),c},t.wrap_message_id=function(a){var b=typeof a;if("string"===b)return t.wrap_string(a);if("number"===b||a instanceof Number)return t.wrap_ulong(a);throw Error("invalid message id:"+a)};var w={};t.unwrap=function(a,b){if(a instanceof c){if(a.descriptor){var d=w[a.descriptor.value];if(d)return new d(a.value);if(b)return a}var e=t.unwrap(a.value,!0);return t.is_map(a)?
 m(e):e}return Array.isArray(a)?a.map(function(a){return t.unwrap(a,!0)}):a},t.described=function(a,b){var c=Object.create(b);return a.length?(c.descriptor=a.shift(),t.described(a,c)):(c.descriptor=a,c)},t.Reader=function(a){this.buffer=a,this.position=0},t.Reader.prototype.read_typecode=function(){return this.read_uint(1)},t.Reader.prototype.read_uint=function(a){var b=this.position;this.position+=a;var c=a>1?"readUInt"+8*a+"BE":"readUInt8";return this.buffer[c](b)},t.Reader.prototype.read_fixed_width=function(a){var b=this.position;return this.position+=a.width,a.read?a.read(this.buffer,b):this.buffer.slice(b,this.position)},t.Reader.prototype.read_variable_width=function(a){var b=this.read_uint(a.width),c=this.read_bytes(b);return a.encoding?c.toString(a.encoding):c},t.Reader.prototype.read=function(){var a=this.read_constructor(),b=this.read_value(n(a.typecode));return a.descriptor?t.described(a.descriptor,b):b},t.Reader.prototype.read_constructor=function(){var a=this.read_typec
 ode();if(0===a){var b=[];b.push(this.read());for(var c=this.read_constructor();c.descriptor;)b.push(c.descriptor),c=this.read_constructor();return{typecode:c.typecode,descriptor:1===b.length?b[0]:b}}return{typecode:a}},t.Reader.prototype.read_value=function(a){if(0===a.width)return new a;if("fixed"===a.category)return new a(this.read_fixed_width(a));if("variable"===a.category)return new a(this.read_variable_width(a));if("compound"===a.category)return this.read_compound(a);if("array"===a.category)return this.read_array(a);throw Error("Invalid category for type: "+a)},t.Reader.prototype.read_multiple=function(a,b){for(var c=b?b:this.read.bind(this),d=[];d.length<a;)d.push(c.apply(this));return d},t.Reader.prototype.read_size_count=function(a){return{size:this.read_uint(a),count:this.read_uint(a)}},t.Reader.prototype.read_compound=function(a){var b=this.read_size_count(a.width);return new a(this.read_multiple(b.count))},t.Reader.prototype.read_array=function(a){var b=this.read_size_cou
 nt(a.width),c=this.read_constructor(),d=new a(this.read_multiple(b.count,this.read_value.bind(this,n(c.typecode))),c.typecode,c.descriptor);
+return d},t.Reader.prototype.toString=function(){var a="buffer@"+this.position;this.position&&(a+=": ");for(var b=this.position;b<this.buffer.length;b++)b>0&&(a+=","),a+="0x"+Number(this.buffer[b]).toString(16);return a},t.Reader.prototype.reset=function(){this.position=0},t.Reader.prototype.skip=function(a){this.position+=a},t.Reader.prototype.read_bytes=function(a){var b=this.position;return this.position+=a,this.buffer.slice(b,this.position)},t.Reader.prototype.remaining=function(){return this.buffer.length-this.position},t.Writer=function(b){this.buffer=b?b:new a(1024),this.position=0},t.Writer.prototype.toBuffer=function(){return this.buffer.slice(0,this.position)},t.Writer.prototype.ensure=function(b){if(this.buffer.length<b){var c=new a(o(2*this.buffer.length,b));this.buffer.copy(c),this.buffer=c}},t.Writer.prototype.write_typecode=function(a){this.write_uint(a,1)},t.Writer.prototype.write_uint=function(a,b){var c=this.position;this.ensure(this.position+b),this.position+=b;va
 r d=b>1?"writeUInt"+8*b+"BE":"writeUInt8";if(!this.buffer[d])throw Error("Buffer does not define "+d);return this.buffer[d](a,c)},t.Writer.prototype.write_fixed_width=function(a,b){var c=this.position;if(this.ensure(this.position+a.width),this.position+=a.width,a.write)a.write(this.buffer,b,c);else{if(!b.copy)throw Error("Cannot handle write for "+a);b.copy(this.buffer,c)}},t.Writer.prototype.write_variable_width=function(b,c){var d=b.encoding?new a(c,b.encoding):new a(c);this.write_uint(d.length,b.width),this.write_bytes(d)},t.Writer.prototype.write_bytes=function(a){var b=this.position;this.ensure(this.position+a.length),this.position+=a.length,a.copy(this.buffer,b)},t.Writer.prototype.write_constructor=function(a,b){b&&(this.write_typecode(0),this.write(b)),this.write_typecode(a)},t.Writer.prototype.write=function(a){if(!(a instanceof c))throw Error("Cannot write "+JSON.stringify(a));this.write_constructor(a.type.typecode,a.descriptor),this.write_value(a.type,a.value,a.array_cons
 tructor)},t.Writer.prototype.write_value=function(a,b,c){if(0!==a.width)if("fixed"===a.category)this.write_fixed_width(a,b);else if("variable"===a.category)this.write_variable_width(a,b);else if("compound"===a.category)this.write_compound(a,b);else{if("array"!==a.category)throw Error("Invalid category "+a.category+" for type: "+a);this.write_array(a,b,c)}},t.Writer.prototype.backfill_size=function(a,b){var c=this.position-b;this.position=b,this.write_uint(c-a,a),this.position+=c-a},t.Writer.prototype.write_compound=function(a,b){var c=this.position;this.position+=a.width,this.write_uint(b.length,a.width);for(var d=0;d<b.length;d++)void 0===b[d]||null===b[d]?this.write(new t.Null):this.write(b[d]);this.backfill_size(a.width,c)},t.Writer.prototype.write_array=function(a,b,c){var d=this.position;this.position+=a.width,this.write_uint(b.length,a.width),this.write_constructor(c.typecode,c.descriptor);for(var e=n(c.typecode),f=0;f<b.length;f++)this.write_value(e,b[f]);this.backfill_size(a
 .width,d)},t.Writer.prototype.toString=function(){var a="buffer@"+this.position;this.position&&(a+=": ");for(var b=0;b<this.position;b++)b>0&&(a+=","),a+=("00"+Number(this.buffer[b]).toString(16)).slice(-2);return a},t.Writer.prototype.skip=function(a){this.ensure(this.position+a),this.position+=a},t.Writer.prototype.clear=function(){this.buffer.fill(0),this.position=0},t.Writer.prototype.remaining=function(){return this.buffer.length-this.position},t.define_composite=function(a){var b=function(a){this.value=a?a:[]};b.descriptor={numeric:a.code,symbolic:"amqp:"+a.name+":list"},b.prototype.dispatch=function(b,c){b["on_"+a.name](c)};for(var c=0;c<a.fields.length;c++){var d=a.fields[c];Object.defineProperty(b.prototype,d.name,r(c,d))}return b.toString=function(){return a.name+"#"+Number(a.code).toString(16)},b.prototype.toJSON=function(){var a={};for(var b in this)"value"!==b&&this[b]&&(a[b]=this[b]);return a},b.create=function(a){var c=new b;for(var d in a)c[d]=a[d];return c},b.protot
 ype.described=function(){return t.described(t.wrap_ulong(b.descriptor.numeric),t.wrap_list(this.value))},b},s({name:"error",code:29,fields:[{name:"condition",type:"symbol",mandatory:!0},{name:"description",type:"string"},{name:"info",type:"map"}]}),b.exports=t}).call(this,a("buffer").Buffer)},{buffer:16}],13:[function(a,b,c){"use strict";var d={};d.generate_uuid=function(){var a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0,c="x"===a?b:3&b|8;return c.toString(16)});return a},d.clone=function(a){for(var b=Object.create(a.prototype||{}),c=Object.getOwnPropertyNames(a),d=0;d<c.length;d++){var e=c[d];b[e]=a[e]}return b},b.exports=d},{}],14:[function(a,b,c){(function(a){"use strict";function c(a){return a}function d(b){return new a(b instanceof ArrayBuffer?new Uint8Array(b):b)}function e(a){return new Uint8Array(a)}function f(a){var b=c,f=c;return a.binaryType&&(a.binaryType="arraybuffer",b=d,f=e),{end:function(){a.close()},write:function(b){
 try{a.send(f(b),{binary:!0})}catch(c){a.onerror(c)}},on:function(c,d){"data"===c?a.onmessage=function(a){d(b(a.data))}:"end"===c?a.onclose=d:"error"===c?a.onerror=d:console.log("ERROR: Attempt to set unrecognised handler on websocket wrapper: "+c)},get_id_string:function(){return a.url}}}b.exports={connect:function(a){return function(b,c,d){return function(){return{connect:function(e,g,h,i){var j=new a(b,c,d);return j.onopen=i,f(j)}}}}},wrap:f}}).call(this,a("buffer").Buffer)},{buffer:16}],15:[function(a,b,c){},{}],16:[function(a,b,c){(function(b){"use strict";function d(){try{var a=new Uint8Array(1);return a.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===a.foo()&&"function"==typeof a.subarray&&0===a.subarray(1,1).byteLength}catch(b){return!1}}function e(){return g.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function f(a,b){if(e()<b)throw new RangeError("Invalid typed array length");return g.TYPED_ARRAY_SUPPORT?(a=new Uint8Array(b),a.__proto__=g.prototype):(
 null===a&&(a=new g(b)),a.length=b),a}function g(a,b,c){if(!(g.TYPED_ARRAY_SUPPORT||this instanceof g))return new g(a,b,c);if("number"==typeof a){if("string"==typeof b)throw new Error("If encoding is specified then the first argument must be a string");return k(this,a)}return h(this,a,b,c)}function h(a,b,c,d){if("number"==typeof b)throw new TypeError('"value" argument must not be a number');return"undefined"!=typeof ArrayBuffer&&b instanceof ArrayBuffer?n(a,b,c,d):"string"==typeof b?l(a,b,c):o(a,b)}function i(a){if("number"!=typeof a)throw new TypeError('"size" argument must be a number');if(0>a)throw new RangeError('"size" argument must not be negative')}function j(a,b,c,d){return i(b),0>=b?f(a,b):void 0!==c?"string"==typeof d?f(a,b).fill(c,d):f(a,b).fill(c):f(a,b)}function k(a,b){if(i(b),a=f(a,0>b?0:0|p(b)),!g.TYPED_ARRAY_SUPPORT)for(var c=0;b>c;++c)a[c]=0;return a}function l(a,b,c){if(("string"!=typeof c||""===c)&&(c="utf8"),!g.isEncoding(c))throw new TypeError('"encoding" must be
  a valid string encoding');var d=0|r(b,c);a=f(a,d);var e=a.write(b,c);return e!==d&&(a=a.slice(0,e)),a}function m(a,b){var c=b.length<0?0:0|p(b.length);a=f(a,c);for(var d=0;c>d;d+=1)a[d]=255&b[d];return a}function n(a,b,c,d){if(b.byteLength,0>c||b.byteLength<c)throw new RangeError("'offset' is out of bounds");if(b.byteLength<c+(d||0))throw new RangeError("'length' is out of bounds");return b=void 0===c&&void 0===d?new Uint8Array(b):void 0===d?new Uint8Array(b,c):new Uint8Array(b,c,d),g.TYPED_ARRAY_SUPPORT?(a=b,a.__proto__=g.prototype):a=m(a,b),a}function o(a,b){if(g.isBuffer(b)){var c=0|p(b.length);return a=f(a,c),0===a.length?a:(b.copy(a,0,0,c),a)}if(b){if("undefined"!=typeof ArrayBuffer&&b.buffer instanceof ArrayBuffer||"length"in b)return"number"!=typeof b.length||Y(b.length)?f(a,0):m(a,b);if("Buffer"===b.type&&_(b.data))return m(a,b.data)}throw new TypeError("First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.")}function p(a){if(a>=e())throw new Ra
 ngeError("Attempt to allocate Buffer larger than maximum size: 0x"+e().toString(16)+" bytes");return 0|a}function q(a){return+a!=a&&(a=0),g.alloc(+a)}function r(a,b){if(g.isBuffer(a))return a.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(a)||a instanceof ArrayBuffer))return a.byteLength;"string"!=typeof a&&(a=""+a);var c=a.length;if(0===c)return 0;for(var d=!1;;)switch(b){case"ascii":case"latin1":case"binary":return c;case"utf8":case"utf-8":case void 0:return T(a).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*c;case"hex":return c>>>1;case"base64":return W(a).length;default:if(d)return T(a).length;b=(""+b).toLowerCase(),d=!0}}function s(a,b,c){var d=!1;if((void 0===b||0>b)&&(b=0),b>this.length)return"";if((void 0===c||c>this.length)&&(c=this.length),0>=c)return"";if(c>>>=0,b>>>=0,b>=c)return"";for(a||(a="utf8");;)switch(a){case"hex":return H(this,b,c);case"utf8":case"utf-8":return D(this,b,c);case"ascii":re
 turn F(this,b,c);case"latin1":case"binary":return G(this,b,c);case"base64":return C(this,b,c);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(this,b,c);default:if(d)throw new TypeError("Unknown encoding: "+a);a=(a+"").toLowerCase(),d=!0}}function t(a,b,c){var d=a[b];a[b]=a[c],a[c]=d}function u(a,b,c,d,e){if(0===a.length)return-1;if("string"==typeof c?(d=c,c=0):c>2147483647?c=2147483647:-2147483648>c&&(c=-2147483648),c=+c,isNaN(c)&&(c=e?0:a.length-1),0>c&&(c=a.length+c),c>=a.length){if(e)return-1;c=a.length-1}else if(0>c){if(!e)return-1;c=0}if("string"==typeof b&&(b=g.from(b,d)),g.isBuffer(b))return 0===b.length?-1:v(a,b,c,d,e);if("number"==typeof b)return b=255&b,g.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?e?Uint8Array.prototype.indexOf.call(a,b,c):Uint8Array.prototype.lastIndexOf.call(a,b,c):v(a,[b],c,d,e);throw new TypeError("val must be string, number or Buffer")}function v(a,b,c,d,e){function f(a,b){return 1===g?a[b]:a.readUInt16BE(b*g)}va
 r g=1,h=a.length,i=b.length;if(void 0!==d&&(d=String(d).toLowerCase(),"ucs2"===d||"ucs-2"===d||"utf16le"===d||"utf-16le"===d)){if(a.length<2||b.length<2)return-1;g=2,h/=2,i/=2,c/=2}var j;if(e){var k=-1;for(j=c;h>j;j++)if(f(a,j)===f(b,-1===k?0:j-k)){if(-1===k&&(k=j),j-k+1===i)return k*g}else-1!==k&&(j-=j-k),k=-1}else for(c+i>h&&(c=h-i),j=c;j>=0;j--){for(var l=!0,m=0;i>m;m++)if(f(a,j+m)!==f(b,m)){l=!1;break}if(l)return j}return-1}function w(a,b,c,d){c=Number(c)||0;var e=a.length-c;d?(d=Number(d),d>e&&(d=e)):d=e;var f=b.length;if(f%2!==0)throw new TypeError("Invalid hex string");d>f/2&&(d=f/2);for(var g=0;d>g;++g){var h=parseInt(b.substr(2*g,2),16);if(isNaN(h))return g;a[c+g]=h}return g}function x(a,b,c,d){return X(T(b,a.length-c),a,c,d)}function y(a,b,c,d){return X(U(b),a,c,d)}function z(a,b,c,d){return y(a,b,c,d)}function A(a,b,c,d){return X(W(b),a,c,d)}function B(a,b,c,d){return X(V(b,a.length-c),a,c,d)}function C(a,b,c){return 0===b&&c===a.length?Z.fromByteArray(a):Z.fromByteArray(
 a.slice(b,c))}function D(a,b,c){c=Math.min(a.length,c);for(var d=[],e=b;c>e;){var f=a[e],g=null,h=f>239?4:f>223?3:f>191?2:1;if(c>=e+h){var i,j,k,l;switch(h){case 1:128>f&&(g=f);break;case 2:i=a[e+1],128===(192&i)&&(l=(31&f)<<6|63&i,l>127&&(g=l));break;case 3:i=a[e+1],j=a[e+2],128===(192&i)&&128===(192&j)&&(l=(15&f)<<12|(63&i)<<6|63&j,l>2047&&(55296>l||l>57343)&&(g=l));break;case 4:i=a[e+1],j=a[e+2],k=a[e+3],128===(192&i)&&128===(192&j)&&128===(192&k)&&(l=(15&f)<<18|(63&i)<<12|(63&j)<<6|63&k,l>65535&&1114112>l&&(g=l))}}null===g?(g=65533,h=1):g>65535&&(g-=65536,d.push(g>>>10&1023|55296),g=56320|1023&g),d.push(g),e+=h}return E(d)}function E(a){var b=a.length;if(aa>=b)return String.fromCharCode.apply(String,a);for(var c="",d=0;b>d;)c+=String.fromCharCode.apply(String,a.slice(d,d+=aa));return c}function F(a,b,c){var d="";c=Math.min(a.length,c);for(var e=b;c>e;++e)d+=String.fromCharCode(127&a[e]);return d}function G(a,b,c){var d="";c=Math.min(a.length,c);for(var e=b;c>e;++e)d+=String.from
 CharCode(a[e]);return d}function H(a,b,c){var d=a.length;(!b||0>b)&&(b=0),(!c||0>c||c>d)&&(c=d);for(var e="",f=b;c>f;++f)e+=S(a[f]);return e}function I(a,b,c){for(var d=a.slice(b,c),e="",f=0;f<d.length;f+=2)e+=String.fromCharCode(d[f]+256*d[f+1]);return e}function J(a,b,c){if(a%1!==0||0>a)throw new RangeError("offset is not uint");if(a+b>c)throw new RangeError("Trying to access beyond buffer length")}function K(a,b,c,d,e,f){if(!g.isBuffer(a))throw new TypeError('"buffer" argument must be a Buffer instance');if(b>e||f>b)throw new RangeError('"value" argument is out of bounds');if(c+d>a.length)throw new RangeError("Index out of range")}function L(a,b,c,d){0>b&&(b=65535+b+1);for(var e=0,f=Math.min(a.length-c,2);f>e;++e)a[c+e]=(b&255<<8*(d?e:1-e))>>>8*(d?e:1-e)}function M(a,b,c,d){0>b&&(b=4294967295+b+1);for(var e=0,f=Math.min(a.length-c,4);f>e;++e)a[c+e]=b>>>8*(d?e:3-e)&255}function N(a,b,c,d,e,f){if(c+d>a.length)throw new RangeError("Index out of range");if(0>c)throw new RangeError("I
 ndex out of range")}function O(a,b,c,d,e){return e||N(a,b,c,4,3.4028234663852886e38,-3.4028234663852886e38),$.write(a,b,c,d,23,4),c+4}function P(a,b,c,d,e){return e||N(a,b,c,8,1.7976931348623157e308,-1.7976931348623157e308),$.write(a,b,c,d,52,8),c+8}function Q(a){if(a=R(a).replace(ba,""),a.length<2)return"";for(;a.length%4!==0;)a+="=";return a}function R(a){return a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")}function S(a){return 16>a?"0"+a.toString(16):a.toString(16)}function T(a,b){b=b||1/0;for(var c,d=a.length,e=null,f=[],g=0;d>g;++g){if(c=a.charCodeAt(g),c>55295&&57344>c){if(!e){if(c>56319){(b-=3)>-1&&f.push(239,191,189);continue}if(g+1===d){(b-=3)>-1&&f.push(239,191,189);continue}e=c;continue}if(56320>c){(b-=3)>-1&&f.push(239,191,189),e=c;continue}c=(e-55296<<10|c-56320)+65536}else e&&(b-=3)>-1&&f.push(239,191,189);if(e=null,128>c){if((b-=1)<0)break;f.push(c)}else if(2048>c){if((b-=2)<0)break;f.push(c>>6|192,63&c|128)}else if(65536>c){if((b-=3)<0)break;f.push(c>>12|224,c>>6&63|128
 ,63&c|128)}else{if(!(1114112>c))throw new Error("Invalid code point");if((b-=4)<0)break;f.push(c>>18|240,c>>12&63|128,c>>6&63|128,63&c|128)}}return f}function U(a){for(var b=[],c=0;c<a.length;++c)b.push(255&a.charCodeAt(c));return b}function V(a,b){for(var c,d,e,f=[],g=0;g<a.length&&!((b-=2)<0);++g)c=a.charCodeAt(g),d=c>>8,e=c%256,f.push(e),f.push(d);return f}function W(a){return Z.toByteArray(Q(a))}function X(a,b,c,d){for(var e=0;d>e&&!(e+c>=b.length||e>=a.length);++e)b[e+c]=a[e];return e}function Y(a){return a!==a}var Z=a("base64-js"),$=a("ieee754"),_=a("isarray");c.Buffer=g,c.SlowBuffer=q,c.INSPECT_MAX_BYTES=50,g.TYPED_ARRAY_SUPPORT=void 0!==b.TYPED_ARRAY_SUPPORT?b.TYPED_ARRAY_SUPPORT:d(),c.kMaxLength=e(),g.poolSize=8192,g._augment=function(a){return a.__proto__=g.prototype,a},g.from=function(a,b,c){return h(null,a,b,c)},g.TYPED_ARRAY_SUPPORT&&(g.prototype.__proto__=Uint8Array.prototype,g.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&g[Symbol.species]===g&&Obje
 ct.defineProperty(g,Symbol.species,{value:null,configurable:!0})),g.alloc=function(a,b,c){return j(null,a,b,c)},g.allocUnsafe=function(a){return k(null,a)},g.allocUnsafeSlow=function(a){return k(null,a)},g.isBuffer=function(a){return!(null==a||!a._isBuffer)},g.compare=function(a,b){if(!g.isBuffer(a)||!g.isBuffer(b))throw new TypeError("Arguments must be Buffers");if(a===b)return 0;for(var c=a.length,d=b.length,e=0,f=Math.min(c,d);f>e;++e)if(a[e]!==b[e]){c=a[e],d=b[e];break}return d>c?-1:c>d?1:0},g.isEncoding=function(a){switch(String(a).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},g.concat=function(a,b){if(!_(a))throw new TypeError('"list" argument must be an Array of Buffers');if(0===a.length)return g.alloc(0);var c;if(void 0===b)for(b=0,c=0;c<a.length;++c)b+=a[c].length;var d=g.allocUnsafe(b),e=0;for(c=0;c<a.length;++c){var f=a[c];if(!g.isBuffer(f))
 throw new TypeError('"list" argument must be an Array of Buffers');f.copy(d,e),e+=f.length}return d},g.byteLength=r,g.prototype._isBuffer=!0,g.prototype.swap16=function(){var a=this.length;if(a%2!==0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(var b=0;a>b;b+=2)t(this,b,b+1);return this},g.prototype.swap32=function(){var a=this.length;if(a%4!==0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var b=0;a>b;b+=4)t(this,b,b+3),t(this,b+1,b+2);return this},g.prototype.swap64=function(){var a=this.length;if(a%8!==0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(var b=0;a>b;b+=8)t(this,b,b+7),t(this,b+1,b+6),t(this,b+2,b+5),t(this,b+3,b+4);return this},g.prototype.toString=function(){var a=0|this.length;return 0===a?"":0===arguments.length?D(this,0,a):s.apply(this,arguments)},g.prototype.equals=function(a){if(!g.isBuffer(a))throw new TypeError("Argument must be a Buffer");return this===a?!0:0===g.compare(this,a)},g.proto
 type.inspect=function(){var a="",b=c.INSPECT_MAX_BYTES;return this.length>0&&(a=this.toString("hex",0,b).match(/.{2}/g).join(" "),this.length>b&&(a+=" ... ")),"<Buffer "+a+">"},g.prototype.compare=function(a,b,c,d,e){if(!g.isBuffer(a))throw new TypeError("Argument must be a Buffer");if(void 0===b&&(b=0),void 0===c&&(c=a?a.length:0),void 0===d&&(d=0),void 0===e&&(e=this.length),0>b||c>a.length||0>d||e>this.length)throw new RangeError("out of range index");if(d>=e&&b>=c)return 0;if(d>=e)return-1;if(b>=c)return 1;if(b>>>=0,c>>>=0,d>>>=0,e>>>=0,this===a)return 0;for(var f=e-d,h=c-b,i=Math.min(f,h),j=this.slice(d,e),k=a.slice(b,c),l=0;i>l;++l)if(j[l]!==k[l]){f=j[l],h=k[l];break}return h>f?-1:f>h?1:0},g.prototype.includes=function(a,b,c){return-1!==this.indexOf(a,b,c)},g.prototype.indexOf=function(a,b,c){return u(this,a,b,c,!0)},g.prototype.lastIndexOf=function(a,b,c){return u(this,a,b,c,!1)},g.prototype.write=function(a,b,c,d){if(void 0===b)d="utf8",c=this.length,b=0;else if(void 0===c&&
 "string"==typeof b)d=b,c=this.length,b=0;else{if(!isFinite(b))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");b=0|b,isFinite(c)?(c=0|c,void 0===d&&(d="utf8")):(d=c,c=void 0)}var e=this.length-b;if((void 0===c||c>e)&&(c=e),a.length>0&&(0>c||0>b)||b>this.length)throw new RangeError("Attempt to write outside buffer bounds");d||(d="utf8");for(var f=!1;;)switch(d){case"hex":return w(this,a,b,c);case"utf8":case"utf-8":return x(this,a,b,c);case"ascii":return y(this,a,b,c);case"latin1":case"binary":return z(this,a,b,c);case"base64":return A(this,a,b,c);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return B(this,a,b,c);default:if(f)throw new TypeError("Unknown encoding: "+d);d=(""+d).toLowerCase(),f=!0}},g.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var aa=4096;g.prototype.slice=function(a,b){var c=this.length;a=~~a,b=void 0===b?c:~~b,0>a?(a+=c,0>a&&(a=0)):a>c&&(a=c),0>b?(b+=c,0>b&&(b=0
 )):b>c&&(b=c),a>b&&(b=a);var d;if(g.TYPED_ARRAY_SUPPORT)d=this.subarray(a,b),d.__proto__=g.prototype;else{var e=b-a;d=new g(e,void 0);for(var f=0;e>f;++f)d[f]=this[f+a]}return d},g.prototype.readUIntLE=function(a,b,c){a=0|a,b=0|b,c||J(a,b,this.length);for(var d=this[a],e=1,f=0;++f<b&&(e*=256);)d+=this[a+f]*e;return d},g.prototype.readUIntBE=function(a,b,c){a=0|a,b=0|b,c||J(a,b,this.length);for(var d=this[a+--b],e=1;b>0&&(e*=256);)d+=this[a+--b]*e;return d},g.prototype.readUInt8=function(a,b){return b||J(a,1,this.length),this[a]},g.prototype.readUInt16LE=function(a,b){return b||J(a,2,this.length),this[a]|this[a+1]<<8},g.prototype.readUInt16BE=function(a,b){return b||J(a,2,this.length),this[a]<<8|this[a+1]},g.prototype.readUInt32LE=function(a,b){return b||J(a,4,this.length),(this[a]|this[a+1]<<8|this[a+2]<<16)+16777216*this[a+3]},g.prototype.readUInt32BE=function(a,b){return b||J(a,4,this.length),16777216*this[a]+(this[a+1]<<16|this[a+2]<<8|this[a+3])},g.prototype.readIntLE=function(a
 ,b,c){a=0|a,b=0|b,c||J(a,b,this.length);for(var d=this[a],e=1,f=0;++f<b&&(e*=256);)d+=this[a+f]*e;return e*=128,d>=e&&(d-=Math.pow(2,8*b)),d},g.prototype.readIntBE=function(a,b,c){a=0|a,b=0|b,c||J(a,b,this.length);for(var d=b,e=1,f=this[a+--d];d>0&&(e*=256);)f+=this[a+--d]*e;return e*=128,f>=e&&(f-=Math.pow(2,8*b)),f},g.prototype.readInt8=function(a,b){return b||J(a,1,this.length),128&this[a]?-1*(255-this[a]+1):this[a]},g.prototype.readInt16LE=function(a,b){b||J(a,2,this.length);var c=this[a]|this[a+1]<<8;return 32768&c?4294901760|c:c},g.prototype.readInt16BE=function(a,b){b||J(a,2,this.length);var c=this[a+1]|this[a]<<8;return 32768&c?4294901760|c:c},g.prototype.readInt32LE=function(a,b){return b||J(a,4,this.length),this[a]|this[a+1]<<8|this[a+2]<<16|this[a+3]<<24},g.prototype.readInt32BE=function(a,b){return b||J(a,4,this.length),this[a]<<24|this[a+1]<<16|this[a+2]<<8|this[a+3]},g.prototype.readFloatLE=function(a,b){return b||J(a,4,this.length),$.read(this,a,!0,23,4)},g.prototype.
 readFloatBE=function(a,b){return b||J(a,4,this.length),$.read(this,a,!1,23,4)},g.prototype.readDoubleLE=function(a,b){return b||J(a,8,this.length),$.read(this,a,!0,52,8)},g.prototype.readDoubleBE=function(a,b){return b||J(a,8,this.length),$.read(this,a,!1,52,8)},g.prototype.writeUIntLE=function(a,b,c,d){if(a=+a,b=0|b,c=0|c,!d){var e=Math.pow(2,8*c)-1;K(this,a,b,c,e,0)}var f=1,g=0;for(this[b]=255&a;++g<c&&(f*=256);)this[b+g]=a/f&255;return b+c},g.prototype.writeUIntBE=function(a,b,c,d){if(a=+a,b=0|b,c=0|c,!d){var e=Math.pow(2,8*c)-1;K(this,a,b,c,e,0)}var f=c-1,g=1;for(this[b+f]=255&a;--f>=0&&(g*=256);)this[b+f]=a/g&255;return b+c},g.prototype.writeUInt8=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,1,255,0),g.TYPED_ARRAY_SUPPORT||(a=Math.floor(a)),this[b]=255&a,b+1},g.prototype.writeUInt16LE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,2,65535,0),g.TYPED_ARRAY_SUPPORT?(this[b]=255&a,this[b+1]=a>>>8):L(this,a,b,!0),b+2},g.prototype.writeUInt16BE=function(a,b,c){return a=+a,b=0|b,
 c||K(this,a,b,2,65535,0),g.TYPED_ARRAY_SUPPORT?(this[b]=a>>>8,this[b+1]=255&a):L(this,a,b,!1),b+2},g.prototype.writeUInt32LE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,4,4294967295,0),g.TYPED_ARRAY_SUPPORT?(this[b+3]=a>>>24,this[b+2]=a>>>16,this[b+1]=a>>>8,this[b]=255&a):M(this,a,b,!0),b+4},g.prototype.writeUInt32BE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,4,4294967295,0),g.TYPED_ARRAY_SUPPORT?(this[b]=a>>>24,this[b+1]=a>>>16,this[b+2]=a>>>8,this[b+3]=255&a):M(this,a,b,!1),b+4},g.prototype.writeIntLE=function(a,b,c,d){if(a=+a,b=0|b,!d){var e=Math.pow(2,8*c-1);K(this,a,b,c,e-1,-e)}var f=0,g=1,h=0;for(this[b]=255&a;++f<c&&(g*=256);)0>a&&0===h&&0!==this[b+f-1]&&(h=1),this[b+f]=(a/g>>0)-h&255;return b+c},g.prototype.writeIntBE=function(a,b,c,d){if(a=+a,b=0|b,!d){var e=Math.pow(2,8*c-1);K(this,a,b,c,e-1,-e)}var f=c-1,g=1,h=0;for(this[b+f]=255&a;--f>=0&&(g*=256);)0>a&&0===h&&0!==this[b+f+1]&&(h=1),this[b+f]=(a/g>>0)-h&255;return b+c},g.prototype.writeInt8=function(a,b,c){retur
 n a=+a,b=0|b,c||K(this,a,b,1,127,-128),g.TYPED_ARRAY_SUPPORT||(a=Math.floor(a)),0>a&&(a=255+a+1),this[b]=255&a,b+1},g.prototype.writeInt16LE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,2,32767,-32768),g.TYPED_ARRAY_SUPPORT?(this[b]=255&a,this[b+1]=a>>>8):L(this,a,b,!0),b+2},g.prototype.writeInt16BE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,2,32767,-32768),g.TYPED_ARRAY_SUPPORT?(this[b]=a>>>8,this[b+1]=255&a):L(this,a,b,!1),b+2},g.prototype.writeInt32LE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,4,2147483647,-2147483648),g.TYPED_ARRAY_SUPPORT?(this[b]=255&a,this[b+1]=a>>>8,this[b+2]=a>>>16,this[b+3]=a>>>24):M(this,a,b,!0),b+4},g.prototype.writeInt32BE=function(a,b,c){return a=+a,b=0|b,c||K(this,a,b,4,2147483647,-2147483648),0>a&&(a=4294967295+a+1),g.TYPED_ARRAY_SUPPORT?(this[b]=a>>>24,this[b+1]=a>>>16,this[b+2]=a>>>8,this[b+3]=255&a):M(this,a,b,!1),b+4},g.prototype.writeFloatLE=function(a,b,c){return O(this,a,b,!0,c)},g.prototype.writeFloatBE=function(a,b,c){return O(t
 his,a,b,!1,c)},g.prototype.writeDoubleLE=function(a,b,c){return P(this,a,b,!0,c)},g.prototype.writeDoubleBE=function(a,b,c){return P(this,a,b,!1,c)},g.prototype.copy=function(a,b,c,d){if(c||(c=0),d||0===d||(d=this.length),b>=a.length&&(b=a.length),b||(b=0),d>0&&c>d&&(d=c),d===c)return 0;if(0===a.length||0===this.length)return 0;if(0>b)throw new RangeError("targetStart out of bounds");if(0>c||c>=this.length)throw new RangeError("sourceStart out of bounds");if(0>d)throw new RangeError("sourceEnd out of bounds");d>this.length&&(d=this.length),a.length-b<d-c&&(d=a.length-b+c);var e,f=d-c;if(this===a&&b>c&&d>b)for(e=f-1;e>=0;--e)a[e+b]=this[e+c];else if(1e3>f||!g.TYPED_ARRAY_SUPPORT)for(e=0;f>e;++e)a[e+b]=this[e+c];else Uint8Array.prototype.set.call(a,this.subarray(c,c+f),b);return f},g.prototype.fill=function(a,b,c,d){if("string"==typeof a){if("string"==typeof b?(d=b,b=0,c=this.length):"string"==typeof c&&(d=c,c=this.l

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org


[04/10] qpid-dispatch git commit: DISPATCH-531 Initial version of openstack horizon plugin

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
new file mode 100644
index 0000000..8ed64a1
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
@@ -0,0 +1,1428 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+  'use strict';
+
+  /**
+   * @method OverviewController
+   * @param $scope
+   * @param QDRService
+   * @param QDRChartServer
+   * dialogServer
+   * $location
+   *
+   * Controller that handles the QDR overview page
+   */
+  angular
+    .module('horizon.dashboard.dispatch.overv')
+    .controller('horizon.dashboard.dispatch.overv.OverviewController', OverviewController);
+
+  OverviewController.$inject = [
+    '$scope',
+    'horizon.dashboard.dispatch.comService',
+    'horizon.dashboard.dispatch.chartService',
+    '$location',
+    '$timeout',
+    'horizon.dashboard.dispatch.overv.basePath',
+    'uiGridConstants',
+  ];
+
+  var FILTERKEY = "QDROverviewFilters"
+  function OverviewController(
+    $scope,
+    QDRService,
+    QDRChartService,
+    $location,
+    $timeout,
+    basePath,
+    uiGridConstants) {
+
+    var ctrl = this;
+
+    QDR.log.debug("QDR.OverviewController started");
+
+    QDRService.addConnectAction( function () {
+      Overview(
+        $scope,
+        QDRService,
+        QDRChartService,
+        $location,
+        $timeout,
+        basePath,
+        uiGridConstants);
+    })
+    QDRService.loadConnectOptions(QDRService.connect);
+
+    $scope.filter = angular.fromJson(localStorage[FILTERKEY]) || {endpointsOnly: true, hideConsoles: true};
+    var showConsoleLinksTitle = function () {
+      return ($scope.filter.hideConsoles ? "Show" : "Hide") + " Console Links"
+    }
+    var showHideConsoleMenuItem = {
+      title: showConsoleLinksTitle(),
+      action: function($event) {
+        $scope.filter.hideConsoles = !$scope.filter.hideConsoles
+        // assumes this is always the 1st custom menu item added
+        this.context.col.colDef.menuItems[0].title = showConsoleLinksTitle()
+        $timeout($scope.allLinkInfo)
+      },
+    }
+    var endpointLinksTitle = function () {
+      return "Show" + ($scope.filter.endpointsOnly ? " all link types" : " endpoints only")
+    }
+
+    $scope.allRouterFields = [];
+    $scope.allRouters = {
+      saveKey: 'allRouters',
+      data: 'allRouterFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+       {
+           field: 'routerId',
+           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'
+       }
+      ],
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      onRegisterApi: function(gridApi){
+        gridApi.selection.on.rowSelectionChanged($scope,function(row){
+          $scope.setActivated('router' , row.entity.nodeId, 'nodeId')
+        });
+      }
+    };
+    $scope.routerFields = []
+    $scope.routerGrid = {
+      saveKey: 'routerGrid',
+      data: 'routerFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+          saveKey: 'routerGrid',
+        },
+        {
+          field: 'value',
+           displayName: 'Value',
+        }
+      ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+    $scope.addressesData = []
+    $scope.addressesGrid = {
+      saveKey: 'addressesGrid',
+      data: 'addressesData',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+        {
+          field: 'address',
+          saveKey: 'addressesGrid',
+          displayName: 'address'
+        },
+        {
+          field: 'class',
+          displayName: 'class'
+        },
+        {
+          field: 'phase',
+          displayName: 'phase',
+          width: 80,
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'inproc',
+          width: 80,
+          displayName: 'in-proc'
+        },
+        {
+          field: 'local',
+          displayName: 'local',
+          width: 80,
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'remote',
+          displayName: 'remote',
+          width: 80,
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'in',
+          displayName: 'in',
+          cellClass: 'grid-align-value'
+        },
+        {
+          field: 'out',
+          displayName: 'out',
+          cellClass: 'grid-align-value'
+        }
+      ],
+
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      onRegisterApi: function(gridApi){
+        gridApi.selection.on.rowSelectionChanged($scope,function(row){
+          $scope.setActivated('address', row.entity.uid, 'uid')
+        });
+      }
+    };
+    // get info for a all links
+    $scope.linkFields = []
+    $scope.linksGrid = {
+      saveKey: 'linksGrid',
+      data: 'linkFields',
+      enableFiltering: true,
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+        {
+          saveKey: 'linksGrid',
+          field: 'link',
+          displayName: 'Link',
+          filter: {placeholder: 'filter link name...'},
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'linkType',
+          displayName: 'Link type',
+          filter: {
+            term: $scope.filter.endpointsOnly ? 'endpoint' : '',
+            placeholder: 'filter link type...',
+          },
+          menuItems: [showHideConsoleMenuItem,
+          {
+            title: endpointLinksTitle(),
+            action: function($event) {
+              $scope.filter.endpointsOnly = !$scope.filter.endpointsOnly
+              // assumes this is the 2nd custom menu item added
+              this.context.col.colDef.menuItems[1].title = endpointLinksTitle()
+              this.context.col.filters[0].term = $scope.filter.endpointsOnly ? 'endpoint' : ''
+              $timeout($scope.allLinkInfo)
+            },
+          }],
+        },
+        {
+          field: 'linkDir',
+          displayName: 'Link dir',
+          filter: {placeholder: 'filter link dir...'},
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'adminStatus',
+          displayName: 'Admin status',
+          filter: {placeholder: 'filter admin status...'},
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'operStatus',
+          displayName: 'Oper status',
+          filter: {placeholder: 'filter oper status...'},
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'deliveryCount',
+          displayName: 'Delivery Count',
+          enableFiltering: false,
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'rate',
+          displayName: 'Rate',
+          enableFiltering: false,
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'uncounts',
+          displayName: 'Outstanding',
+          enableFiltering: false,
+          menuItems: [showHideConsoleMenuItem],
+        },
+        {
+          field: 'owningAddr',
+          displayName: 'Address',
+          filter: {placeholder: 'filter address...'},
+          menuItems: [showHideConsoleMenuItem],
+        }/*,
+        {
+          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%'
+                }*/
+            ],
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnReordering: true,
+      showColumnMenu: true,
+      rowTemplate: 'dispatch/tplLinkRow.html',
+      onRegisterApi: function(gridApi){
+        gridApi.selection.on.rowSelectionChanged($scope,function(row){
+          $scope.setActivated('link', row.entity.uid, 'uid')
+        });
+        gridApi.core.on.filterChanged( $scope, function() {
+          var column = this.grid.columns[1];
+          $scope.filter.endpointsOnly = (column.filters[0].term === 'endpoint' )
+          column.colDef.menuItems[1].title = endpointLinksTitle()
+        });
+      }
+    };
+    $scope.allConnectionFields = []
+    $scope.allConnectionGrid = {
+      saveKey: 'allConnGrid',
+      data: 'allConnectionFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      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'
+        }
+      ],
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      onRegisterApi: function(gridApi){
+        gridApi.selection.on.rowSelectionChanged($scope,function(row){
+          $scope.setActivated('connection', row.entity.uid, 'uid')
+        });
+      }
+    };
+    $scope.addressFields = []
+    $scope.addressGrid = {
+      saveKey: 'addGrid',
+      data: 'addressFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+          saveKey: 'addGrid',
+        },
+        {
+          field: 'value',
+          displayName: 'Value',
+        }
+      ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+    $scope.singleLinkFields = []
+    $scope.linkGrid = {
+      saveKey: 'linkGrid',
+      data: 'singleLinkFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+        {
+          field: 'attribute',
+          displayName: 'Attribute',
+        },
+        {
+          field: 'value',
+          displayName: 'Value',
+        }
+      ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+    $scope.connectionFields = []
+    $scope.connectionGrid = {
+        saveKey: 'connGrid',
+      data: 'connectionFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+                 {
+                     field: 'attribute',
+                     displayName: 'Attribute',
+                    saveKey: 'connGrid',
+                 },
+                 {
+                     field: 'value',
+                     displayName: 'Value',
+                 }
+            ],
+      enableColumnResize: true,
+      multiSelect: false
+    }
+    $scope.allLogFields = []
+    $scope.allLogGrid = {
+      saveKey: 'allLogGrid',
+      data: 'allLogFields',
+      enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
+      columnDefs: [
+        {
+          field: 'module',
+          saveKey: 'allLogGrid',
+          displayName: 'Module'
+        },
+        {
+          field: 'enable',
+          displayName: 'Enable'
+        },
+        {
+          field: 'count',
+          displayName: 'Count'
+        }
+      ],
+      enableRowSelection: true,
+      enableRowHeaderSelection: false,
+      multiSelect: false,
+      enableColumnResize: true,
+      enableColumnReordering: true,
+      onRegisterApi: function(gridApi){
+        gridApi.selection.on.rowSelectionChanged($scope,function(row){
+          $scope.setActivated('log', row.entity.module, 'module')
+        });
+      }
+    };
+  }
+
+  function Overview (
+    $scope,
+    QDRService,
+    QDRChartService,
+    $location,
+    $timeout,
+    basePath,
+    uiGridConstants) {
+
+    var COLUMNSTATEKEY = 'QDRColumnKey.';
+    var OVERVIEWEXPANDEDKEY = "QDROverviewExpanded"
+    var OVERVIEWACTIVATEDKEY = "QDROverviewActivated"
+    var OVERVIEWMODEIDS = "QDROverviewModeIds"
+
+    // 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 (workspace) { 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 (workspace) { 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;
+    }
+    var refreshInterval = 5000
+    $scope.modes = [
+      {title: 'Overview', name: 'Overview', right: false}
+     ];
+
+    // get info for all routers
+    var allRouterInfo = function () {
+      var nodeIds = QDRService.nodeIdList()
+      var expected = Object.keys(nodeIds).length
+      var received = 0;
+      var allRouterFields = [];
+      var gotNodeInfo = function (nodeName, entity, response) {
+        var results = response.results;
+        var name = QDRService.nameFromId(nodeName)
+        var connections = 0;
+        results.forEach( function (result) {
+          var role = QDRService.valFor(response.attributeNames, result, "role")
+          if (role != 'inter-router') {
+            ++connections
+          }
+        })
+        allRouterFields.push({routerId: name, connections: connections, nodeId: nodeName})
+        ++received
+        if (expected == received) {
+          allRouterFields.sort ( function (a,b) { return a.routerId < b.routerId ? -1 : a.routerId > b.routerId ? 1 : 0})
+          // now get each router's node info
+          QDRService.getMultipleNodeInfo(nodeIds, "router", [], function (nodeIds, entity, responses) {
+            for(var r in responses) {
+              var result = responses[r]
+              var routerId = QDRService.valFor(result.attributeNames, result.results[0], "id")
+              allRouterFields.some( function (connField) {
+                if (routerId === connField.routerId) {
+                  result.attributeNames.forEach ( function (attrName) {
+                    connField[attrName] = QDRService.valFor(result.attributeNames, result.results[0], attrName)
+                  })
+                  connField['routerId'] = connField.id; // like QDR.A
+                  return true
+                }
+                return false
+              })
+            }
+            $scope.allRouterFields = allRouterFields
+            updateRouterTree(allRouterFields)
+          }, nodeIds[0], false)
+        }
+      }
+      nodeIds.forEach ( function (nodeId, i) {
+        QDRService.getNodeInfo(nodeId, ".connection", ["role"], gotNodeInfo)
+      })
+      loadColState($scope.allRouters)
+    }
+
+    // get info for a single router
+    var routerInfo = function (node) {
+      $scope.router = node
+
+      $scope.routerFields = [];
+      var fields = Object.keys(node.fields)
+      fields.forEach( function (field) {
+        var attr = (field === 'connections') ? 'External connections' : field
+        $scope.routerFields.push({attribute: attr, value: node.fields[field]})
+      })
+      loadColState($scope.routerGrid);
+    }
+
+    // get info for all addresses
+    var allAddressInfo = function () {
+      var gotAllAddressFields = function ( addressFields ) {
+        if (!addressFields || addressFields.length === 0)
+          return;
+        // update the grid's data
+        addressFields.sort ( function (a,b) { return a.address < b.address ? -1 : a.address > b.address ? 1 : 0})
+        addressFields[0].title = addressFields[0].address
+        for (var i=1; i<addressFields.length; ++i) {
+          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
+
+        // repopulate the tree's child nodes
+        updateAddressTree(addressFields)
+      }
+      getAllAddressFields(gotAllAddressFields)
+      loadColState($scope.addressesGrid);
+    }
+
+    var getAllAddressFields = function (callback) {
+      var nodeIds = QDRService.nodeIdList()
+      var addr_class = function (addr) {
+        if (!addr) return "-"
+            if (addr[0] == 'M')  return "mobile"
+            if (addr[0] == 'R')  return "router"
+            if (addr[0] == 'A')  return "area"
+            if (addr[0] == 'L')  return "local"
+            if (addr[0] == 'C')  return "link-incoming"
+            if (addr[0] == 'D')  return "link-outgoing"
+            if (addr[0] == 'T')  return "topo"
+            return "unknown: " + addr[0]
+      }
+
+      var addr_phase = function (addr) {
+            if (!addr)
+                return "-"
+            if (addr[0] == 'M')
+                return addr[1]
+            return ''
+      }
+
+      var addressFields = []
+      QDRService.getMultipleNodeInfo(nodeIds, "router.address", [], function (nodeIds, entity, response) {
+        response.aggregates.forEach( function (result) {
+          var prettySum = function (field) {
+            var fieldIndex = response.attributeNames.indexOf(field)
+            if (fieldIndex < 0) {
+              return "-"
+            }
+            var val = result[fieldIndex].sum
+            return QDRService.pretty(val)
+          }
+
+          var uid = QDRService.valFor(response.attributeNames, result, "identity").sum
+          var identity = QDRService.identity_clean(uid)
+
+          addressFields.push({
+            address: QDRService.addr_text(identity),
+            'class': QDRService.addr_class(identity),
+            phase:   addr_phase(identity),
+            inproc:  prettySum("inProcess"),
+            local:   prettySum("subscriberCount"),
+            remote:  prettySum("remoteCount"),
+            'in':    prettySum("deliveriesIngress"),
+            out:     prettySum("deliveriesEgress"),
+            thru:    prettySum("deliveriesTransit"),
+            toproc:  prettySum("deliveriesToContainer"),
+            fromproc:prettySum("deliveriesFromContainer"),
+            uid:     uid
+          })
+        })
+        callback(addressFields)
+      }, nodeIds[0])
+    }
+
+    var updateLinkGrid = function ( linkFields ) {
+      // apply the filter
+
+      var filteredLinks = linkFields.filter( function (link) {
+        var include = true;
+/*
+        if ($scope.filter.endpointsOnly === true) {
+          if (link.linkType !== 'endpoint')
+            include = false;
+        }
+*/
+        if ($scope.filter.hideConsoles) {
+          if (QDRService.isConsoleLink(link))
+            include = false;
+        }
+        return include;
+      })
+      $scope.linkFields = filteredLinks;
+    }
+
+    $scope.$watch("filter", function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        $timeout($scope.allLinkInfo);
+        localStorage[FILTERKEY] = JSON.stringify($scope.filter)
+      }
+    }, true)
+
+    $scope.$on('ngGridEventColumns', function (e, columns) {
+      var saveInfo = columns.map( function (col) {
+        return [col.width, col.visible]
+      })
+      var saveKey = columns[0].colDef.saveKey
+      if (saveKey)
+        localStorage.setItem(COLUMNSTATEKEY+saveKey, JSON.stringify(saveInfo));
+    })
+
+    var loadColState = function (grid) {
+      if (!grid)
+        return;
+      var columns = localStorage.getItem(COLUMNSTATEKEY+grid.saveKey);
+      if (columns) {
+        var cols = JSON.parse(columns);
+        cols.forEach( function (col, index) {
+          if (grid.columnDefs[index]) {
+            grid.columnDefs[index].width = col[0];
+            grid.columnDefs[index].visible = col[1]
+          }
+        })
+      }
+    }
+
+    $scope.allLinkInfo = function () {
+      getAllLinkFields([updateLinkGrid, updateLinkTree])
+//      loadColState($scope.linksGrid);
+    }
+
+    var getAllLinkFields = function (completionCallbacks, selectionCallback) {
+      var nodeIds = QDRService.nodeIdList()
+      var linkFields = []
+      var now = Date.now()
+      var rate = function (linkName, response, result) {
+        if (!$scope.linkFields)
+          return 0;
+        var oldname = $scope.linkFields.filter(function (link) {
+          return link.link === linkName
+        })
+        if (oldname.length === 1) {
+          var elapsed = (now - oldname[0].timestamp) / 1000;
+          if (elapsed < 0)
+            return 0
+          var delivered = QDRService.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
+        }
+      }
+      var expected = nodeIds.length;
+      var received = 0;
+      var gotLinkInfo = function (nodeName, entity, response) {
+        response.results.forEach( function (result) {
+          var nameIndex = response.attributeNames.indexOf('name')
+          var prettyVal = function (field) {
+            var fieldIndex = response.attributeNames.indexOf(field)
+            if (fieldIndex < 0) {
+              return "-"
+            }
+            var val = result[fieldIndex]
+            return QDRService.pretty(val)
+          }
+          var uncounts = function () {
+            var und = QDRService.valFor(response.attributeNames, result, "undeliveredCount")
+            var uns = QDRService.valFor(response.attributeNames, result, "unsettledCount")
+            return QDRService.pretty(und + uns)
+          }
+          var linkName = function () {
+            var namestr = QDRService.nameFromId(nodeName)
+            return namestr + ':' + QDRService.valFor(response.attributeNames, result, "identity")
+          }
+          var fixAddress = function () {
+            var addresses = []
+            var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr") || ""
+            var rawAddress = owningAddr
+            /*
+                 - "L*"  =>  "* (local)"
+                 - "M0*" =>  "* (direct)"
+                 - "M1*" =>  "* (dequeue)"
+                 - "MX*" =>  "* (phase X)"
+            */
+            var address = undefined;
+            var starts = {'L': '(local)', 'M0': '(direct)', 'M1': '(dequeue)'}
+            for (var start in starts) {
+              if (owningAddr.startsWith(start)) {
+                var 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') {
+                  var phase = parseInt(owningAddress.substr(1))
+                  if (phase && !isNaN(phase)) {
+                    var phaseStr = phase + "";
+                    var star = owningAddress.substr(2 + phaseStr.length)
+                    address = star + " " + "(phase " + phaseStr + ")"
+                  }
+                }
+              }
+            }
+            addresses[0] = address || owningAddr
+            addresses[1] = rawAddress
+            return addresses
+          }
+          if (!selectionCallback || selectionCallback(response, result)) {
+            var adminStatus = QDRService.valFor(response.attributeNames, result, "adminStatus")
+            var operStatus = QDRService.valFor(response.attributeNames, result, "operStatus")
+            var linkName = linkName()
+            var linkType = QDRService.valFor(response.attributeNames, result, "linkType")
+            var rawRate = rate(linkName, response, result)
+            var addresses = fixAddress();
+            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.pretty(rawRate),
+              rawRate: rawRate,
+              capacity: QDRService.valFor(response.attributeNames, result, "capacity"),
+              undeliveredCount: QDRService.valFor(response.attributeNames, result, "undeliveredCount"),
+              unsettledCount: QDRService.valFor(response.attributeNames, result, "unsettledCount"),
+
+              rawAddress: addresses[1],
+              rawDeliveryCount: QDRService.valFor(response.attributeNames, result, "deliveryCount"),
+              name: QDRService.valFor(response.attributeNames, result, "name"),
+              linkName: QDRService.valFor(response.attributeNames, result, "linkName"),
+              connectionId: QDRService.valFor(response.attributeNames, result, "connectionId"),
+              linkDir: QDRService.valFor(response.attributeNames, result, "linkDir"),
+              linkType: linkType,
+              peer: QDRService.valFor(response.attributeNames, result, "peer"),
+              type: QDRService.valFor(response.attributeNames, result, "type"),
+
+              uid:     linkName,
+              timestamp: now,
+              nodeId: nodeName,
+              identity: QDRService.valFor(response.attributeNames, result, "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.getNodeInfo(nodeId, "router.link", [], gotLinkInfo);
+      })
+    }
+
+    // get info for a all connections
+    var allConnectionInfo = function () {
+      getAllConnectionFields([updateConnectionGrid, updateConnectionTree])
+          loadColState($scope.allConnectionGrid);
+    }
+    // called after conection data is available
+    var updateConnectionGrid = function (connectionFields) {
+      $scope.allConnectionFields = connectionFields;
+    }
+
+    // get the connection data for all nodes
+    // called periodically
+    // creates a connectionFields array and calls the callbacks (updateTree and updateGrid)
+    var getAllConnectionFields = function (callbacks) {
+      var nodeIds = QDRService.nodeIdList()
+      var connectionFields = [];
+      var expected = nodeIds.length;
+      var received = 0;
+      var gotConnectionInfo = function (nodeName, entity, response) {
+        response.results.forEach( function (result) {
+
+          var auth = "no_auth"
+          var sasl = QDRService.valFor(response.attributeNames, result, "sasl")
+          if (QDRService.valFor(response.attributeNames, result, "isAuthenticated")) {
+            auth = sasl
+            if (sasl === "ANONYMOUS")
+              auth = "anonymous-user"
+            else {
+              if (sasl === "GSSAPI")
+                sasl = "Kerberos"
+              if (sasl === "EXTERNAL")
+                sasl = "x.509"
+              auth = QDRService.valFor(response.attributeNames, result, "user") + "(" +
+                  QDRService.valFor(response.attributeNames, result, "sslCipher") + ")"
+              }
+          }
+
+          var sec = "no-security"
+          if (QDRService.valFor(response.attributeNames, result, "isEncrypted")) {
+            if (sasl === "GSSAPI")
+              sec = "Kerberos"
+            else
+              sec = QDRService.valFor(response.attributeNames, result, "sslProto") + "(" +
+                  QDRService.valFor(response.attributeNames, result, "sslCipher") + ")"
+          }
+
+          var host = QDRService.valFor(response.attributeNames, result, "host")
+          var connField = {
+            host: host,
+            security: sec,
+            authentication: auth,
+            routerId: nodeName,
+            uid: host + QDRService.valFor(response.attributeNames, result, "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 < b.host ? -1 : a.host > b.host ? 1 : 0})
+          callbacks.forEach( function (cb) {
+            cb(connectionFields)
+          })
+        }
+      }
+      nodeIds.forEach( function (nodeId) {
+        QDRService.getNodeInfo(nodeId, ".connection", [], gotConnectionInfo)
+      })
+    }
+
+    // get info for a single address
+    var addressInfo = function (address) {
+      if (!address)
+        return;
+      $scope.address = address
+      var currentEntity = getCurrentLinksEntity();
+      // we are viewing the addressLinks page
+      if (currentEntity === 'Address' && entityModes[currentEntity].currentModeId === 'links') {
+        updateModeLinks()
+        return;
+      }
+
+      $scope.addressFields = [];
+      var fields = Object.keys(address.fields)
+      fields.forEach( function (field) {
+        if (field != "title" && field != "uid")
+          $scope.addressFields.push({attribute: field, value: address.fields[field]})
+      })
+      loadColState($scope.addressGrid);
+    }
+
+    // display the grid detail info for a single link
+    var linkInfo = function (link) {
+      if (!link)
+        return;
+
+      $scope.link = link;
+      $scope.singleLinkFields = [];
+      var fields = Object.keys(link.fields)
+      var excludeFields = ["title", "uid", "uncounts", "rawDeliveryCount", "timestamp", "rawAddress"]
+      fields.forEach( function (field) {
+        if (excludeFields.indexOf(field) == -1)
+          $scope.singleLinkFields.push({attribute: field, value: link.fields[field]})
+      })
+      loadColState($scope.linkGrid);
+    }
+
+    // get info for a single connection
+    $scope.gridModes = [{
+          content: '<a><i class="icon-list"></i> Attriutes</a>',
+      id: 'attributes',
+      title: "View attributes"
+      },
+      {
+          content: '<a><i class="icon-link"></i> Links</a>',
+          id: 'links',
+          title: "Show links"
+      }
+      ];
+    var saveModeIds = function () {
+      var 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()
+    var entityModes = {
+        Address: {
+            currentModeId: savedModeIds.Address,
+            filter: function (response, result) {
+              var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr")
+              var id = $scope.address.data.fields.uid
+              return (owningAddr === $scope.address.data.fields.uid)
+            }
+        },
+        Connection: {
+            currentModeId: savedModeIds.Connection,
+            filter: function (response, result) {
+        var connectionId = QDRService.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 = [];
+        updateModeLinks();
+      }
+    }
+    $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) {
+      $timeout(function () {$scope.linkFields = linkFields})
+    }
+    // based on which entity is selected, get and filter the links
+    var updateModeLinks = function () {
+      var currentEntity = getCurrentLinksEntity()
+      if (!currentEntity)
+        return;
+      var selectionCallback = entityModes[currentEntity].filter;
+      getAllLinkFields([updateEntityLinkGrid], selectionCallback)
+    }
+    var getCurrentLinksEntity = function () {
+      var currentEntity;
+      var active = $("#overtree").dynatree("getActiveNode");
+      if (active) {
+        currentEntity = active.data.type;
+      }
+      return currentEntity;
+    }
+
+    $scope.quiesceLinkClass = function (row) {
+      var stateClassMap = {
+        enabled: 'btn-primary',
+        disabled: 'btn-danger'
+      }
+      return stateClassMap[row.entity.adminStatus]
+    }
+
+    $scope.quiesceLink = function (row, $event) {
+      QDRService.quiesceLink(row.entity.nodeId, row.entity.name);
+      $event.stopPropagation()
+    }
+
+    $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").dynatree("getRoot").visit(function(node){
+                node.expand(true);
+            });
+    }
+    $scope.contractAll = function () {
+      $("#overtree").dynatree("getRoot").visit(function(node){
+                node.expand(false);
+            })
+    }
+
+    var connectionInfo = function (connection) {
+      if (!connection)
+        return;
+      $scope.connection = connection
+
+      var currentEntity = getCurrentLinksEntity();
+      // we are viewing the connectionLinks page
+      if (currentEntity === 'Connection' && entityModes[currentEntity].currentModeId === 'links') {
+        updateModeLinks()
+        return;
+      }
+
+      $scope.connectionFields = [];
+      var fields = Object.keys(connection.fields)
+      fields.forEach( function (field) {
+        if (field != "title" && field != "uid")
+          $scope.connectionFields.push({attribute: field, value: connection.fields[field]})
+      })
+      $scope.selectMode($scope.currentMode)
+      loadColState($scope.connectionGrid);
+    }
+
+    // get info for a all logs
+    var allLogEntries = []
+    var allLogInfo = function () {
+      var nodeIds = QDRService.nodeIdList()
+      var expected = nodeIds.length;
+      var received = 0;
+      var logResults = []
+      var gotLogInfo = function (nodeId, entity, response, context) {
+        var statusCode = context.message.application_properties.statusCode;
+        if (statusCode < 200 || statusCode >= 300) {
+          Core.notification('error', context.message.application_properties.statusDescription);
+          //QDR.log.debug(context.message.application_properties.statusDescription)
+          return;
+        }
+        var logFields = response.map( function (result) {
+          return {
+            nodeId: QDRService.nameFromId(nodeId),
+            name: result[0],
+            type: result[1],
+            message: result[2],
+            source: result[3],
+            line: result[4],
+            time: Date(result[5]).toString()
+          }
+        })
+        logResults.push.apply(logResults, logFields) // append new array to existing
+        if (expected == ++received) {
+          logResults.sort( function (a, b) {
+            return b.name - a.name
+          })
+
+          $scope.allLogFields = [];
+          var options = $scope.data.log.options;
+          options.forEach( function (option) {
+            if (option.id != 'all') {
+              $scope.allLogFields.push(
+                {module: option.id,
+                enable: option.fields.enable,
+                count: logResults.filter( function (entry) {
+                  return entry.name === option.fields.module
+                }).length
+              })
+            }
+          })
+          allLogEntries = logResults
+        }
+      }
+      nodeIds.forEach( function (node) {
+        QDRService.sendMethod(node, undefined, {}, "GET-LOG", {}, gotLogInfo)
+      })
+
+    }
+
+    // get info for a single log
+    var logInfo = function (node) {
+      $scope.log = node
+      $scope.logFields = allLogEntries.filter( function (log) {
+        return node.id === log.name
+      })
+      $scope.$apply();
+    }
+
+    var updateExpanded = function () {
+      if (!$scope.selectedObject)
+        return;
+      // find the parent of the selectedObject and call it's info function
+      if ($scope.selectedObject.pinfo)
+        $scope.selectedObject.pinfo()
+      $scope.selectedObject.info($scope.selectedObject)
+    }
+
+    var sendChartRequest = function (svgCharts) {
+      var gotChartData = function (linkFields) {
+        var now = new Date();
+        svgCharts.forEach( function (svgChart) {
+          var cutOff = new Date(now.getTime() - svgChart.chart.duration() * 60 * 1000);
+          var name = svgChart.chart.name()
+          var attr = svgChart.chart.attr()
+          var data = svgChart.chart.data(name, attr) // get a reference to the data array
+          var val = svgChart.chart.getVal(linkFields)
+          data.push([now, val])
+          // expire the old data
+          while (data[0][0] < cutOff) {
+            data.shift();
+          }
+        })
+      }
+      getAllLinkFields([gotChartData])
+    }
+
+    // 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;
+      lastKey = 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 () {
+      var list = getExpandedList();
+      localStorage[OVERVIEWEXPANDEDKEY] = JSON.stringify(list)
+      expandedNodeList = list
+    }
+
+    $scope.setActivated = function (dropdown, uid, suid) {
+      $("#sel" + dropdown).val(uid)
+
+      var dd = $scope.data[dropdown];
+      var newOption;
+      if (uid == 'all') {
+        newOption = dd.options[0];
+      } else {
+        dd.options.some( function (option) {
+          if (option.fields && option.fields[suid] === uid) {
+            newOption = option
+            return true;
+          } else
+            return false;
+        })
+      }
+      $scope.activated(newOption)
+    }
+
+    $scope.selectedObject = null;
+    $scope.templateUrl = null;
+    // activated is called each time a dropdown is changed
+    // based on which node is clicked, load the correct data grid template and start getting the data
+    $scope.activated = function (node) {
+      if (!node)
+        return;
+      $scope.selectedObject = node;
+      if (node.id !== "all") {
+        $scope.data[node.type].sel = node
+      }
+      //saveExpanded()
+      //saveActivated(node.data.key)
+
+      $scope.templateUrl = 'dispatch/' + node.type + ".html";
+      // the node's info function will fetch the grids data
+      if (node.info) {
+        $timeout(function () {node.info(node)})
+      }
+    }
+
+    /* --------------------------------------------------
+    *
+    * setup the dropdowns
+    *
+    * -------------------------------------------------
+    */
+    var initDropDown = function (dd, info, type) {
+      $scope.data[dd] = {}
+      $scope.data[dd].options = getAllOption(dd, info, type)
+      $scope.data[dd].sel = $scope.data[dd].options[0];
+    }
+    var getAllOption = function (dd, info, type) {
+      return [{id: 'all',
+        name: 'All ' + type,
+        info: info,
+        type: type}]
+    }
+    var updateDropdown = function (dropdown, allFields, idKey, nameKey, allInfo, allType, info) {
+      var currentId = $scope.data[dropdown].sel.id;
+      $scope.data[dropdown].options = getAllOption(dropdown, allInfo, allType)
+      allFields.forEach( function (fields) {
+        var option = {id: fields[idKey],
+                     name: fields[nameKey],
+                     info: info,
+                     type: dropdown,
+                     fields: fields,
+                     pinfo: allInfo}
+        $scope.data[dropdown].options.push(option);
+
+        if (currentId === option.id) {
+          $scope.data[dropdown].sel = option
+        }
+      })
+      if ($scope.selectedObject && $scope.selectedObject.type === dropdown) {
+        $scope.selectedObject = $scope.data[dropdown].sel;
+//QDR.log.debug("updated " + dropdown + " to ")
+//console.dump($scope.selectedObject)
+      }
+    }
+
+    // get saved tree state
+    var lastKey = loadActivatedNode();
+    // called when the list of routers changes
+    var updateRouterTree = function (routerFields) {
+      updateDropdown('router', routerFields, 'nodeId', 'routerId', allRouterInfo, 'routers', routerInfo)
+    }
+    var updateAddressTree = function (addressFields) {
+      updateDropdown('address', addressFields, 'uid', 'title', allAddressInfo, 'addresss', addressInfo)
+    }
+    // called whenever a background update is done and an option in the link dropdown is selected
+    var updateLinkTree = function (linkFields) {
+      updateDropdown('link', linkFields, 'uid', 'title', $scope.allLinkInfo, 'links', linkInfo)
+    }
+    var updateConnectionTree = function (connectionFields) {
+      updateDropdown('connection', connectionFields, 'uid', 'host', allConnectionInfo, 'connections', connectionInfo)
+    }
+
+    $scope.data = {}
+    initDropDown('router', allRouterInfo, 'routers')
+    initDropDown('address', allAddressInfo, 'addresss')
+    initDropDown('link', $scope.allLinkInfo, 'links', true)
+    initDropDown('connection', allConnectionInfo, 'connections')
+    initDropDown('log', allLogInfo, 'logs')
+    // called after we are connected to initialize data
+    var initTreeState = function () {
+      allRouterInfo();
+      allAddressInfo();
+      $scope.allLinkInfo();
+      allConnectionInfo()
+
+      var nodeIds = QDRService.nodeIdList()
+      QDRService.getNodeInfo(nodeIds[0], "log", ["module", "enable"], function (nodeName, entity, response) {
+        var moduleIndex = response.attributeNames.indexOf('module')
+        response.results.sort( function (a,b) {return a[moduleIndex] < b[moduleIndex] ? -1 : a[moduleIndex] > b[moduleIndex] ? 1 : 0})
+        response.results.forEach( function (result) {
+          var entry = QDRService.flatten(response.attributeNames, result)
+          $scope.data.log.options.push({id: entry.module, name: entry.module, info: logInfo, type: 'log', fields: entry, pinfo: allLogInfo});
+        })
+        initTreeAndGrid();
+      })
+    }
+
+    $scope.svgCharts = [];
+    var updateTimer;
+    var initCharts = function () {
+      var charts = [];
+      charts.push(QDRChartService.createRequestAndChart(
+        {
+         attr: 'Outstanding deliveries',
+         nodeId: '',
+         name: 'for all endpoints',
+         entity: 'router.link',
+         visibleDuration: 1,
+         duration: 1,
+        }))
+        charts[charts.length-1].getVal = function (linkFields) {
+          var uncountTotal = 0;
+          linkFields.forEach( function (row) {
+            if (row.linkType == 'endpoint' && !QDRService.isConsoleLink(row))
+              uncountTotal += row.undeliveredCount + row.unsettledCount
+          })
+          return uncountTotal;
+        }
+
+      charts.push(QDRChartService.createRequestAndChart(
+        {
+         //type: "rate",
+         attr: 'Outgoing deliveries per second',
+         nodeId: '',
+         name: 'for all endpoints',
+         entity: 'router.link',
+         visibleDuration: 1,
+         duration: 1,
+        }))
+        charts[charts.length-1].getVal = function (linkFields) {
+          var countTotal = 0.0;
+          linkFields.forEach( function (row) {
+            if (row.linkType == 'endpoint' && !QDRService.isConsoleLink(row) && row.linkDir == "out") {
+              countTotal += parseFloat(row.rawRate + "")
+            }
+          })
+          return countTotal;
+        }
+
+      charts.push(QDRChartService.createRequestAndChart(
+        {
+         //type: "rate",
+         attr: 'Incoming deliveries per second',
+         nodeId: '',
+         name: 'for all endpoints',
+         entity: 'router.link',
+         visibleDuration: 1,
+         duration: 1,
+        }))
+        charts[charts.length-1].getVal = function (linkFields) {
+          var countTotal = 0.0;
+          linkFields.forEach( function (row) {
+            if (row.linkType == 'endpoint' && !QDRService.isConsoleLink(row) && row.linkDir == "in")
+              countTotal += parseFloat(row.rawRate + "")
+          })
+          return countTotal;
+        }
+        charts[charts.length-1].areaColor = "#fcd6d6"
+        charts[charts.length-1].lineColor = "#c70404"
+
+      charts.forEach( function (chart) {
+        $scope.svgCharts.push(new QDRChartService.AreaChart(chart));
+      })
+    }
+    initCharts();
+
+    var initTreeAndGrid = function () {
+
+      // show the All routers page
+      $scope.setActivated('link', 'all')
+
+      // populate the data for each expanded node
+      $timeout(updateExpanded);
+      QDRService.addUpdatedAction( "overview", function () {
+        $timeout(updateExpanded);
+        sendChartRequest($scope.svgCharts)
+      })
+      // update the node list
+      QDRService.startUpdating()
+
+      var showChart = function () {
+        // the chart divs are generated by angular and aren't available immediately
+        var div = angular.element("#" + $scope.svgCharts[0].chart.id());
+        if (!div.width()) {
+          setTimeout(showChart, 100);
+          return;
+        }
+        updateDialogChart();
+      }
+
+      var updateDialogChart = function () {
+        $scope.svgCharts.forEach( function ( svgChart) {
+          svgChart.tick(svgChart.chart.id())
+        })
+        if (updateTimer)
+          clearTimeout(updateTimer)
+        updateTimer = setTimeout(updateDialogChart, 1000);
+      }
+      showChart();
+
+      loadColState($scope.allRouters);
+      loadColState($scope.routerGrid);
+      loadColState($scope.addressesGrid);
+      loadColState($scope.addressGrid);
+      loadColState($scope.linksGrid);
+      loadColState($scope.linkGrid);
+      loadColState($scope.allConnectionGrid);
+      loadColState($scope.connectionGrid);
+    } // end of initTreeAndGrid
+
+    $scope.$on("$destroy", function( event ) {
+      QDRService.stopUpdating()
+      QDRService.delUpdatedAction("overview")
+      if (updateTimer)
+        clearTimeout(updateTimer)
+    });
+    initTreeState();
+    QDRService.addDisconnectAction( function () {
+      QDR.log.debug("disconnected from router. show a toast message");
+      if (updateTimer)
+        clearTimeout(updateTimer)
+    })
+  };
+
+  return QDR;
+
+}(QDR || {}));
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0c58c381/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.module.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.module.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.module.js
new file mode 100644
index 0000000..cdde521
--- /dev/null
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.module.js
@@ -0,0 +1,178 @@
+/*
+ * Licensed 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.
+ */
+(function () {
+  'use strict';
+
+  angular
+    .module('horizon.dashboard.dispatch.overv', [])
+    .config(config)
+    .run(addTemplates)
+
+  config.$inject = [
+    '$provide',
+    '$windowProvider'
+  ];
+
+  addTemplates.$inject = [
+    '$templateCache'
+  ]
+
+  /**
+   * @name config
+   * @param {Object} $provide
+   * @param {Object} $windowProvider
+   * @description Base path for the overview code
+   * @returns {undefined} No return value
+   */
+  function config($provide, $windowProvider) {
+    var path = $windowProvider.$get().STATIC_URL + 'dashboard/dispatch/overv/';
+    $provide.constant('horizon.dashboard.dispatch.overv.basePath', path);
+  }
+
+  function addTemplates($templateCache) {
+    $templateCache.put('dispatch/tplLinkRow.html',
+      "<div" +
+      "    ng-repeat=\"(colRenderIndex, col) in colContainer.renderedColumns track by col.uid\"" +
+      "    ui-grid-one-bind-id-grid=\"rowRenderIndex + '-' + col.uid + '-cell'\"" +
+      "    class=\"ui-grid-cell\"" +
+      "    ng-class=\"{ 'ui-grid-row-header-cell': col.isRowHeader, linkDirIn: row.entity.linkDir=='in', linkDirOut: row.entity.linkDir=='out' }\"" +
+      "    role=\"{{col.isRowHeader ? 'rowheader' : 'gridcell'}}\"" +
+      "    ui-grid-cell>" +
+      "</div>"
+    );
+    $templateCache.put('dispatch/links.html',
+      "<h3>Links</h3>" +
+      "<div class='grid' ui-grid='linksGrid' ui-grid-selection></div>"
+    );
+    $templateCache.put('dispatch/link.html',
+      "<h3>Link {$ link.name $}</h3>" +
+      "<div class='grid noHighlight' ui-grid='linkGrid' ui-grid-resize-columns></div>"
+    );
+    $templateCache.put('dispatch/overview.html',
+      "<div id=\"overview-controller\" ng-controller=\"horizon.dashboard.dispatch.overv.OverviewController as ctrl\">" +
+      "    <div id=\"overview_charts\" class=\"clearfix\">" +
+      "        <div ng-repeat=\"chart in svgCharts\" id=\"{$ chart.chart.id() $}\" class=\"d3Chart\"></div>" +
+      "    </div>" +
+      "    <hr/>" +
+      "    <div id=\"overview_dropdowns\" class=\"clearfix\">" +
+      "        <div class=\"overview-dropdown\"" +
+      "             ng-class=\"{selected1: selectedObject.type == 'router', selected: selectedObject.type == 'routers'}\">" +
+      "            <div class=\"dropdown-entity\">Routers</div>" +
+      "            <select id=\"selrouter\" ng-options=\"option.name for option in data.router.options track by option.id\"" +
+      "                    ng-click=\"activated(data.router.sel)\" ng-model=\"data.router.sel\"></select>" +
+      "        </div>" +
+      "        <div class=\"overview-dropdown\"" +
+      "            ng-class=\"{selected1: selectedObject.type == 'address', selected: selectedObject.type == 'addresses'}\">" +
+      "            <div class=\"dropdown-entity\">Addresses</div>" +
+      "            <select id=\"seladdress\" ng-options=\"option.name for option in data.address.options track by option.id\"" +
+      "                ng-click=\"activated(data.address.sel)\" ng-model=\"data.address.sel\"></select>" +
+      "        </div>" +
+      "        <div class=\"overview-dropdown\"" +
+      "            ng-class=\"{selected1: selectedObject.type == 'link', selected: selectedObject.type == 'links'}\">" +
+      "            <div class=\"dropdown-entity\">Links</div>" +
+      "            <select id=\"sellink\" ng-options=\"option.name for option in data.link.options track by option.id\"" +
+      "                    ng-click=\"activated(data.link.sel)\" ng-model=\"data.link.sel\"></select>" +
+      "        </div>" +
+      "        <div class=\"overview-dropdown\"" +
+      "            ng-class=\"{selected1: selectedObject.type == 'connection', selected: selectedObject.type == 'connections'}\">" +
+      "            <div class=\"dropdown-entity\">Connections</div>" +
+      "            <select id=\"selconnection\" ng-options=\"option.name for option in data.connection.options track by option.id\"" +
+      "                    ng-click=\"activated(data.connection.sel)\" ng-model=\"data.connection.sel\"></select>" +
+      "        </div>" +
+      "        <div class=\"overview-dropdown\"" +
+      "            ng-class=\"{selected1: selectedObject.type == 'log', selected: selectedObject.type == 'logs'}\">" +
+      "            <div class=\"dropdown-entity\">Logs</div>" +
+      "            <select id=\"sellog\" ng-options=\"option.name for option in data.log.options track by option.id\"" +
+      "                    ng-click=\"activated(data.log.sel)\" ng-model=\"data.log.sel\"></select>" +
+      "        </div>" +
+      "    </div>" +
+      "    <div ng-include=\"templateUrl\"></div>" +
+      "    <div ng-init=\"overviewLoaded()\"></div>" +
+      "</div>"
+    );
+    $templateCache.put('dispatch/addresss.html',
+      "<h3>Addresses</h3>" +
+      "<div class='grid' ui-grid='addressesGrid' ui-grid-selection></div>"
+    );
+    $templateCache.put('dispatch/address.html',
+      "<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-unsafe=\"mode.content\"> </li>" +
+      "</ul>" +
+      "<div ng-if=\"isModeVisible('Address','attributes')\" class=\"selectedItems\">" +
+      "    <h3>Address {$ address.name $}</h3>" +
+      "    <div class=\"gridStyle noHighlight\" ui-grid=\"addressGrid\"></div>" +
+      "</div>" +
+      "<div ng-if=\"isModeVisible('Address','links')\" class=\"selectedItems\">" +
+      "    <h3>Links for address {$ address.name $}</h3>" +
+      "    <div class=\"gridStyle\" ui-grid=\"linksGrid\"></div>" +
+      "</div>"
+    );
+
+    $templateCache.put('dispatch/connection.html',
+      "<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-unsafe=\"mode.content\"> </li>" +
+      "</ul>" +
+      "<div ng-if=\"isModeVisible('Connection','attributes')\" class=\"selectedItems\">" +
+      "    <h3>Connection {$ connection.name $}</h3>" +
+      "    <div class=\"gridStyle noHighlight\" ui-grid=\"connectionGrid\"></div>" +
+      "</div>" +
+      "<div ng-if=\"isModeVisible('Connection','links')\" class=\"selectedItems\">" +
+      "    <h3>Links for connection {$ connection.name $}</h3>" +
+      "    <div class=\"gridStyle\" ui-grid=\"linksGrid\"></div>" +
+      "</div>"
+    );
+    $templateCache.put('dispatch/connections.html',
+      "<h3>Connections</h3>" +
+      "<div class=\"overview\">" +
+      "    <div class=\"grid\" ui-grid=\"allConnectionGrid\" ui-grid-selection></div>" +
+      "</div>"
+    );
+    $templateCache.put('dispatch/log.html',
+      "<h3>{$ log.name $}</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.name $}</div>"
+    );
+    $templateCache.put('dispatch/logs.html',
+      "<h3>Recent log entries</h3>" +
+      "<div class=\"overview\">" +
+      "    <div class=\"grid\" ui-grid=\"allLogGrid\" ui-grid-selection></div>" +
+      "</div>"
+    );
+    $templateCache.put('dispatch/router.html',
+      "<h3>Router {$ router.name $}</h3>" +
+      "<div class=\"grid noHighlight\" ui-grid=\"routerGrid\"></div>"
+    );
+    $templateCache.put('dispatch/routers.html',
+      "<h3>Routers</h3>" +
+      "<div class=\"overview\">" +
+      "    <div class=\"grid\" ui-grid=\"allRouters\" ui-grid-selection></div>" +
+      "</div>"
+    );
+  }
+})();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org