You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2018/06/25 21:26:54 UTC

[8/8] qpid-dispatch git commit: DISPATCH-1049 Add console tests

DISPATCH-1049 Add console tests


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

Branch: refs/heads/master
Commit: b5deb03579a7dedd81a56f32baa3e5f4b5b57136
Parents: af99754
Author: Ernest Allen <ea...@redhat.com>
Authored: Mon Jun 25 17:25:50 2018 -0400
Committer: Ernest Allen <ea...@redhat.com>
Committed: Mon Jun 25 17:25:50 2018 -0400

----------------------------------------------------------------------
 .gitignore                                      |    2 +-
 console/CMakeLists.txt                          |  207 +-
 console/stand-alone/.babelrc                    |   21 +
 console/stand-alone/gulpfile.js                 |  101 +-
 console/stand-alone/index.html                  |   11 +-
 console/stand-alone/main.js                     |  254 ++
 console/stand-alone/modules/connection.js       |  347 +++
 console/stand-alone/modules/correlator.js       |   50 +
 console/stand-alone/modules/management.js       |   63 +
 console/stand-alone/modules/topology.js         |  403 +++
 console/stand-alone/modules/utilities.js        |  115 +
 console/stand-alone/package-lock.json           | 2519 ++++++++++++++----
 console/stand-alone/package.json                |   24 +-
 console/stand-alone/plugin/css/dispatch.css     |    4 +-
 console/stand-alone/plugin/html/qdrList.html    |   53 +-
 .../plugin/html/tmplChartConfig.html            |   12 +-
 console/stand-alone/plugin/js/chord/data.js     |  234 +-
 console/stand-alone/plugin/js/chord/filters.js  |   52 +-
 .../plugin/js/chord/layout/layout.js            |    6 +-
 console/stand-alone/plugin/js/chord/matrix.js   |    3 +-
 console/stand-alone/plugin/js/chord/qdrChord.js |   24 +-
 .../plugin/js/chord/ribbon/ribbon.js            |    4 +-
 console/stand-alone/plugin/js/dispatchPlugin.js |  264 --
 .../stand-alone/plugin/js/dlgChartController.js |   31 +-
 console/stand-alone/plugin/js/navbar.js         |  140 +-
 .../stand-alone/plugin/js/posintDirective.js    |   75 +
 .../stand-alone/plugin/js/qdrChartService.js    | 1607 ++++++-----
 console/stand-alone/plugin/js/qdrCharts.js      |   33 +-
 console/stand-alone/plugin/js/qdrGlobals.js     |   58 +-
 console/stand-alone/plugin/js/qdrList.js        | 1732 ++++++------
 console/stand-alone/plugin/js/qdrListChart.js   |  146 -
 console/stand-alone/plugin/js/qdrOverview.js    |   85 +-
 .../plugin/js/qdrOverviewChartsController.js    |   18 +-
 .../plugin/js/qdrOverviewLogsController.js      |   25 +-
 console/stand-alone/plugin/js/qdrSchema.js      |   22 +-
 console/stand-alone/plugin/js/qdrService.js     |  317 +--
 console/stand-alone/plugin/js/qdrSettings.js    |   89 +-
 .../plugin/js/qdrTopAddressesController.js      |   16 +-
 console/stand-alone/plugin/js/topology/links.js |  216 ++
 console/stand-alone/plugin/js/topology/nodes.js |  162 ++
 .../plugin/js/topology/qdrTopology.js           | 2512 +++++++----------
 .../stand-alone/plugin/js/topology/topoUtils.js |  225 ++
 .../stand-alone/plugin/js/topology/traffic.js   |  445 ++++
 .../stand-alone/plugin/js/topology/traffic.ts   |  443 ---
 console/stand-alone/test/filter.js              |   73 +
 console/stand-alone/test/links.js               |   82 +
 console/stand-alone/test/matrix.js              |   51 +
 console/stand-alone/test/nodes.json             |    1 +
 console/stand-alone/test/utilities.js           |  192 ++
 console/stand-alone/tsconfig.json               |   23 +-
 console/stand-alone/vendor-js.txt               |   12 +-
 51 files changed, 8155 insertions(+), 5449 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 49a9c44..d7c32e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,7 @@ tests/policy-1/policy-*.json
 .metadata
 .settings
 console/test/topolgies/config-*
-.history
+.history/
 .tox
 .vscode
 console/stand-alone/node_modules/

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt
index bfd9cd8..a55b572 100644
--- a/console/CMakeLists.txt
+++ b/console/CMakeLists.txt
@@ -20,101 +20,122 @@
 ##
 ## Add cmake option to choose whether to install stand-alone console
 ##
-option(CONSOLE_INSTALL "Build and install console (requires npm)" ON)
+option(CONSOLE_INSTALL "Build and install console (requires npm 5.2+)" ON)
 
 if(CONSOLE_INSTALL)
-  find_program(NPX_EXE npx DOC "Location of the npx task runner")
-    if (NPX_EXE)
-
-      set(CONSOLE_SOURCE_DIR "${CMAKE_SOURCE_DIR}/console/stand-alone")
-      set(CONSOLE_BUILD_DIR "${CMAKE_BINARY_DIR}/console")
-
-      ## Files needed to create the ${CONSOLE_ARTIFACTS}
-      file (GLOB_RECURSE CONSOLE_JS_SOURCES ${CONSOLE_SOURCE_DIR}/plugin/js/*.js)
-      file (GLOB_RECURSE CONSOLE_TS_SOURCES ${CONSOLE_SOURCE_DIR}/plugin/js/*.ts)
-      set(CONSOLE_CSS_SOURCE ${CONSOLE_SOURCE_DIR}/plugin/css/dispatch.css)
-      set(ALL_CONSOLE_SOURCES ${CONSOLE_JS_SOURCES} ${CONSOLE_TS_SOURCES} ${CONSOLE_CSS_SOURCE})
-
-      ## Files created during the console build
-      set(CONSOLE_ARTIFACTS
-        ${CONSOLE_BUILD_DIR}/dist/js/dispatch.min.js
-        ${CONSOLE_BUILD_DIR}/dist/js/vendor.min.js
-        ${CONSOLE_BUILD_DIR}/dist/css/dispatch.min.css
-        ${CONSOLE_BUILD_DIR}/dist/css/vendor.min.css
-      )
-
-      ## copy the build config files
-      configure_file( ${CONSOLE_SOURCE_DIR}/package.json ${CONSOLE_BUILD_DIR}/ COPYONLY)
-      configure_file( ${CONSOLE_SOURCE_DIR}/package-lock.json ${CONSOLE_BUILD_DIR}/ COPYONLY)
-      configure_file( ${CONSOLE_SOURCE_DIR}/tslint.json ${CONSOLE_BUILD_DIR}/ COPYONLY)
-      configure_file( ${CONSOLE_SOURCE_DIR}/gulpfile.js ${CONSOLE_BUILD_DIR}/ COPYONLY)
-      configure_file( ${CONSOLE_SOURCE_DIR}/vendor-js.txt ${CONSOLE_BUILD_DIR}/ COPYONLY)
-      configure_file( ${CONSOLE_SOURCE_DIR}/vendor-css.txt ${CONSOLE_BUILD_DIR}/ COPYONLY)
-
-      ## Tell cmake how and when to build ${CONSOLE_ARTIFACTS}
-      add_custom_command (
-        OUTPUT ${CONSOLE_ARTIFACTS}
-        COMMENT "Running console build"
-        COMMAND npm install --loglevel=error
-        COMMAND ${NPX_EXE} gulp --src ${CONSOLE_SOURCE_DIR}
-        DEPENDS ${ALL_CONSOLE_SOURCES}
-        WORKING_DIRECTORY ${CONSOLE_BUILD_DIR}/
-        )
-
-      ## Ensure ${CONSOLE_ARTIFACTS} is built on a make when needed
-      add_custom_target(console ALL
-        DEPENDS ${CONSOLE_ARTIFACTS}
-      )
-
-      ##
-      ## Install the static and built console files
-      ##
-
-      ## Files copied to the root of the console's install dir
-      set(BASE_FILES
-        ${CONSOLE_SOURCE_DIR}/index.html
-        ${CONSOLE_SOURCE_DIR}/favicon-32x32.png
-      )
-      ## Files copied to the css/ dir
-      set(CSS_FONTS
-        ${CONSOLE_SOURCE_DIR}/plugin/css/brokers.ttf
-        ${CONSOLE_BUILD_DIR}/node_modules/angular-ui-grid/ui-grid.woff
-        ${CONSOLE_BUILD_DIR}/node_modules/angular-ui-grid/ui-grid.ttf
-      )
-      ## Files copied to the fonts/ dir
-      set(VENDOR_FONTS
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-BoldItalic-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Italic-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/fontawesome-webfont.woff2
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/fontawesome-webfont.eot
-        ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf
-        ${CONSOLE_BUILD_DIR}/node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
-      )
-
-      install(DIRECTORY ${CONSOLE_BUILD_DIR}/dist/
-        DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}
-        PATTERN "*.map" EXCLUDE
-      )
-      install(DIRECTORY ${CONSOLE_SOURCE_DIR}/plugin/html/
-        DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/html
-        FILES_MATCHING PATTERN "*.html"
-      )
-      install(FILES ${BASE_FILES}
-        DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}
-      )
-      install(FILES ${CSS_FONTS}
-        DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/css/
-      )
-      install(FILES ${VENDOR_FONTS}
-        DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/fonts/
-      )
-    else(NPX_EXE)
-      message(STATUS "Cannot build console, npm not found")
-    endif(NPX_EXE)
+  find_program (NPM_EXECUTABLE npm DOC "Location of npm package manager")
+
+  if (NPM_EXECUTABLE)
+    execute_process(COMMAND ${NPM_EXECUTABLE} --version
+        OUTPUT_VARIABLE NPM_VERSION)
+    if(${NPM_VERSION} VERSION_EQUAL "5.2.0" OR ${NPM_VERSION} VERSION_GREATER "5.2.0")
+
+      find_program(NPX_EXE npx DOC "Location of the npx task runner")
+        if (NPX_EXE)
+
+          set(CONSOLE_SOURCE_DIR "${CMAKE_SOURCE_DIR}/console/stand-alone")
+          set(CONSOLE_BUILD_DIR "${CMAKE_BINARY_DIR}/console")
+
+          ## Files needed to create the ${CONSOLE_ARTIFACTS}
+          file (GLOB_RECURSE CONSOLE_JS_SOURCES ${CONSOLE_SOURCE_DIR}/plugin/js/*.js)
+          file (GLOB_RECURSE CONSOLE_TS_SOURCES ${CONSOLE_SOURCE_DIR}/plugin/js/*.ts)
+          file (GLOB_RECURSE CONSOLE_MODULE_SOURCES ${CONSOLE_SOURCE_DIR}/modules/*.js)
+          set(CONSOLE_CSS_SOURCE ${CONSOLE_SOURCE_DIR}/plugin/css/dispatch.css)
+          set(CONSOLE_MAIN ${CONSOLE_SOURCE_DIR}/main.js)
+          set(ALL_CONSOLE_SOURCES ${CONSOLE_MAIN} ${CONSOLE_MODULE_SOURCES} ${CONSOLE_JS_SOURCES} ${CONSOLE_TS_SOURCES} ${CONSOLE_CSS_SOURCE})
+
+          ## Files created during the console build
+          set(CONSOLE_ARTIFACTS
+            ${CONSOLE_BUILD_DIR}/dist/js/main.min.js
+            ${CONSOLE_BUILD_DIR}/dist/js/vendor.min.js
+            ${CONSOLE_BUILD_DIR}/dist/css/dispatch.min.css
+            ${CONSOLE_BUILD_DIR}/dist/css/vendor.min.css
+          )
+
+          ## copy the build config files
+          configure_file( ${CONSOLE_SOURCE_DIR}/package.json ${CONSOLE_BUILD_DIR}/ COPYONLY)
+          configure_file( ${CONSOLE_SOURCE_DIR}/package-lock.json ${CONSOLE_BUILD_DIR}/ COPYONLY)
+          configure_file( ${CONSOLE_SOURCE_DIR}/tslint.json ${CONSOLE_BUILD_DIR}/ COPYONLY)
+          configure_file( ${CONSOLE_SOURCE_DIR}/gulpfile.js ${CONSOLE_BUILD_DIR}/ COPYONLY)
+          configure_file( ${CONSOLE_SOURCE_DIR}/vendor-js.txt ${CONSOLE_BUILD_DIR}/ COPYONLY)
+          configure_file( ${CONSOLE_SOURCE_DIR}/vendor-css.txt ${CONSOLE_BUILD_DIR}/ COPYONLY)
+
+          ## Tell cmake how and when to build ${CONSOLE_ARTIFACTS}
+          add_custom_command (
+            OUTPUT ${CONSOLE_ARTIFACTS}
+            COMMENT "Running console build"
+            COMMAND npm install --loglevel=error
+            COMMAND ${NPX_EXE} gulp --src ${CONSOLE_SOURCE_DIR} --build "production"
+            DEPENDS ${ALL_CONSOLE_SOURCES}
+            WORKING_DIRECTORY ${CONSOLE_BUILD_DIR}/
+            )
+
+          ## Ensure ${CONSOLE_ARTIFACTS} is built on a make when needed
+          add_custom_target(console ALL
+            DEPENDS ${CONSOLE_ARTIFACTS}
+          )
+
+          ##
+          ## Install the static and built console files
+          ##
+
+          ## Files copied to the root of the console's install dir
+          set(BASE_FILES
+            ${CONSOLE_SOURCE_DIR}/index.html
+            ${CONSOLE_SOURCE_DIR}/favicon-32x32.png
+          )
+          ## Files copied to the css/ dir
+          set(CSS_FONTS
+            ${CONSOLE_SOURCE_DIR}/plugin/css/brokers.ttf
+          )
+          ## Files copied to the css/fonts/ dir
+          set(CSSFONTS_FONTS
+            ${CONSOLE_BUILD_DIR}/node_modules/angular-ui-grid/fonts/ui-grid.woff
+            ${CONSOLE_BUILD_DIR}/node_modules/angular-ui-grid/fonts/ui-grid.ttf
+          )
+          ## Files copied to the fonts/ dir
+          set(VENDOR_FONTS
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-SemiboldItalic-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-BoldItalic-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/OpenSans-Italic-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/fontawesome-webfont.woff2
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/fontawesome-webfont.eot
+            ${CONSOLE_BUILD_DIR}/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf
+            ${CONSOLE_BUILD_DIR}/node_modules/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
+          )
+
+          install(DIRECTORY ${CONSOLE_BUILD_DIR}/dist/
+            DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}
+            PATTERN "*.map" EXCLUDE
+          )
+          install(DIRECTORY ${CONSOLE_SOURCE_DIR}/plugin/html/
+            DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/html
+            FILES_MATCHING PATTERN "*.html"
+          )
+          install(FILES ${BASE_FILES}
+            DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}
+          )
+          install(FILES ${CSS_FONTS}
+            DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/css/
+          )
+          install(FILES ${CSSFONTS_FONTS}
+            DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/css/fonts/
+          )
+          install(FILES ${VENDOR_FONTS}
+            DESTINATION ${CONSOLE_STAND_ALONE_INSTALL_DIR}/fonts/
+          )
+        else(NPX_EXE)
+          message(STATUS "Cannot build console, npx not found.")
+        endif(NPX_EXE)
+    else(${NPM_VERSION} VERSION_EQUAL "5.2.0" OR ${NPM_VERSION} VERSION_GREATER "5.2.0")
+      message(STATUS "Cannot build console. npm version 5.2 or greater is required.")
+    endif(${NPM_VERSION} VERSION_EQUAL "5.2.0" OR ${NPM_VERSION} VERSION_GREATER "5.2.0")
+  endif(NPM_EXECUTABLE)
+
 endif(CONSOLE_INSTALL)
 
 ##

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/.babelrc
----------------------------------------------------------------------
diff --git a/console/stand-alone/.babelrc b/console/stand-alone/.babelrc
new file mode 100644
index 0000000..d67a300
--- /dev/null
+++ b/console/stand-alone/.babelrc
@@ -0,0 +1,21 @@
+/*
+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.
+*/
+{
+  "presets": ["es2015"]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/gulpfile.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/gulpfile.js b/console/stand-alone/gulpfile.js
index 46531c3..0ddaf10 100644
--- a/console/stand-alone/gulpfile.js
+++ b/console/stand-alone/gulpfile.js
@@ -19,20 +19,26 @@ under the License.
 `;
 
 const gulp = require('gulp'),
-  babel = require('gulp-babel'),
+  mocha = require('gulp-mocha'),
+  gulpif = require('gulp-if'),
+  rollup = require('rollup-stream'),
+  source = require('vinyl-source-stream'),
+  buffer = require('vinyl-buffer'),
   concat = require('gulp-concat'),
   uglify = require('gulp-uglify'),
+  terser = require('gulp-terser'),
+  babel = require('gulp-babel'),
   ngAnnotate = require('gulp-ng-annotate'),
-  rename = require('gulp-rename'),
   cleanCSS = require('gulp-clean-css'),
   del = require('del'),
   eslint = require('gulp-eslint'),
   maps = require('gulp-sourcemaps'),
   insert = require('gulp-insert'),
+  rename = require('gulp-rename'),
   fs = require('fs'),
   tsc = require('gulp-typescript'),
-  tslint = require('gulp-tslint');
-  //tsProject = tsc.createProject('tsconfig.json');
+  tslint = require('gulp-tslint'),
+  through = require('through2');
 
   // temp directory for converted typescript files
 const built_ts = 'built_ts';
@@ -59,6 +65,7 @@ const arg = (argList => {
 })(process.argv);
 
 var src = arg.src ? arg.src + '/' : '';
+var production = (arg.build === 'production');
 
 const paths = {
   typescript: {
@@ -70,10 +77,14 @@ const paths = {
     dest: 'dist/css/'
   },
   scripts: {
-    src: [src + 'plugin/js/**/*.js', built_ts + '/**/*.js'],
+    src: [src + 'plugin/js/**/*.js'],
     dest: 'dist/js/'
   }
 };
+var touch = through.obj(function(file, enc, done) {
+  var now = new Date;
+  fs.utimes(file.path, now, now, done);
+});
 
 function clean() {
   return del(['dist',built_ts ]);
@@ -110,20 +121,6 @@ function vendor_styles() {
     .pipe(gulp.dest(paths.styles.dest));
 }
 
-function scripts() {
-  return gulp.src(paths.scripts.src, { sourcemaps: true })
-    .pipe(babel({
-      presets: [require.resolve('babel-preset-env')]
-    }))
-    .pipe(ngAnnotate())
-    .pipe(maps.init())
-    .pipe(uglify())
-    .pipe(concat('dispatch.min.js'))
-    .pipe(insert.prepend(license))
-    .pipe(maps.write('./'))
-    .pipe(gulp.dest(paths.scripts.dest));
-}
-
 function vendor_scripts() {
   var vendor_lines = fs.readFileSync('vendor-js.txt').toString().split('\n');
   var vendor_files = vendor_lines.filter( function (line) {
@@ -134,7 +131,8 @@ function vendor_scripts() {
     .pipe(uglify())
     .pipe(concat('vendor.min.js'))
     .pipe(maps.write('./'))
-    .pipe(gulp.dest(paths.scripts.dest));
+    .pipe(gulp.dest(paths.scripts.dest))
+    .pipe(touch);
 }
 function watch() {
   gulp.watch(paths.scripts.src, scripts);
@@ -167,10 +165,68 @@ function ts_lint() {
     .pipe(tslint.report());
 }
 
+function scripts() {
+  return rollup({
+    input: src + './main.js',
+    sourcemap: true,
+    format: 'es'
+  }).on('error', e => {
+    console.error(`${e.stack}`);
+  })
+  
+  // point to the entry file and gives the name of the output file.
+    .pipe(source('main.min.js', src))
+  
+  // buffer the output. most gulp plugins, including gulp-sourcemaps, don't support streams.
+    .pipe(buffer())
+  
+  // tell gulp-sourcemaps to load the inline sourcemap produced by rollup-stream.
+    .pipe(maps.init({loadMaps: true}))
+  // transform the code further here.
+  /*
+    .pipe(babel(
+      {presets: [
+        ['env', {
+          targets: {
+            'browsers': [
+              'Chrome >= 52',
+              'FireFox >= 44',
+              'Safari >= 7',
+              'Explorer 11',
+              'last 4 Edge versions'
+            ]
+          },
+          useBuiltIns: true,
+          //debug: true
+        }],
+        'es2015'
+      ],
+      'ignore': [
+        'node_modules'
+      ]
+      }
+    ))
+    */
+    .pipe(ngAnnotate())
+    //.pipe(gulpif(production, uglify()))
+    .pipe(gulpif(production, terser()))
+    .pipe(gulpif(production, insert.prepend(license)))
+  // write the sourcemap alongside the output file.
+    .pipe(maps.write('.'))
+  
+  // and output to ./dist/main.js as normal.
+    .pipe(gulp.dest(paths.scripts.dest));
+}
+
+function test () {
+  return gulp.src(['test/**/*.js'], {read: false})
+    .pipe(mocha({require: ['babel-core/register'], exit: true}))
+    .on('error', console.error);
+}
+
 var build = gulp.series(
   clean,                          // removes the dist/ dir
-  gulp.parallel(lint, ts_lint),   // lints the .js, .ts files
-  typescript,                     // converts .ts to .js
+  lint,                           // lints the .js
   gulp.parallel(vendor_styles, vendor_scripts, styles, scripts), // uglify and concat
   cleanup                         // remove .js that were converted from .ts
 );
@@ -186,5 +242,6 @@ exports.tsc = typescript;
 exports.scripts = scripts;
 exports.styles = styles;
 exports.vendor = vendor;
+exports.test = test;
 
 gulp.task('default', build);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/index.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/index.html b/console/stand-alone/index.html
index 68070a3..1f08594 100644
--- a/console/stand-alone/index.html
+++ b/console/stand-alone/index.html
@@ -20,7 +20,6 @@ under the License.
 <html xmlns:ng="https://angularjs.org">
 
 <head>
-
     <meta charset="utf-8"/>
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
@@ -76,9 +75,15 @@ under the License.
     </div>
 </div>
 
+<!-- <script type="module" src="js/main.min.js"></script> -->
 <script type="text/javascript" src="js/vendor.min.js"></script>
-<script type="text/javascript" src="js/dispatch.min.js"></script>
-
+<script type="text/javascript" src="js/main.min.js"></script>
+<script defer nomodule>
+    var installError = document.getElementById('installError');
+    if (installError) {
+        installError.innerHTML = 'This browser is not supported because it does not support es-2015 modules. <a href="https://www.ecma-international.org/ecma-262/6.0/">https://www.ecma-international.org/ecma-262/6.0/</a><br/>Please use a different browser.';
+    }
+</script>
 
 <script>
     // If angular hasn't loaded a page after 1 second, display the error message

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/main.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/main.js b/console/stand-alone/main.js
new file mode 100644
index 0000000..ca69709
--- /dev/null
+++ b/console/stand-alone/main.js
@@ -0,0 +1,254 @@
+/*
+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.
+*/
+/* global angular d3 */
+
+/**
+ * @module QDR
+ * @main QDR
+ *
+ * The main entry point for the QDR module
+ *
+ */
+
+//import angular from 'angular';
+import { QDRLogger, QDRTemplatePath, QDR_LAST_LOCATION } from './plugin/js/qdrGlobals.js';
+import { QDRService } from './plugin/js/qdrService.js';
+import { QDRChartService } from './plugin/js/qdrChartService.js';
+import { NavBarController } from './plugin/js/navbar.js';
+import { OverviewController } from './plugin/js/qdrOverview.js';
+import { OverviewChartsController } from './plugin/js/qdrOverviewChartsController.js';
+import { OverviewLogsController } from './plugin/js/qdrOverviewLogsController.js';
+import { TopologyController } from './plugin/js/topology/qdrTopology.js';
+import { ChordController } from './plugin/js/chord/qdrChord.js';
+import { ListController } from './plugin/js/qdrList.js';
+import { TopAddressesController } from './plugin/js/qdrTopAddressesController.js';
+import { ChartDialogController } from './plugin/js/dlgChartController.js';
+import { SettingsController } from './plugin/js/qdrSettings.js';
+import { SchemaController } from './plugin/js/qdrSchema.js';
+import { ChartsController } from './plugin/js/qdrCharts.js';
+import { posint } from './plugin/js/posintDirective.js';
+
+(function(QDR) {
+
+  /**
+   * This plugin's angularjs module instance
+   */
+  QDR.module = angular.module('QDR', ['ngRoute', 'ngSanitize', 'ngResource', 'ui.bootstrap',
+    'ui.grid', 'ui.grid.selection', 'ui.grid.autoResize', 'ui.grid.resizeColumns', 'ui.grid.saveState',
+    'ui.slider', 'ui.checkbox']);
+
+  // set up the routing for this plugin
+  QDR.module.config(function($routeProvider) {
+    $routeProvider
+      .when('/', {
+        templateUrl: QDRTemplatePath + 'qdrOverview.html'
+      })
+      .when('/overview', {
+        templateUrl: QDRTemplatePath + 'qdrOverview.html'
+      })
+      .when('/topology', {
+        templateUrl: QDRTemplatePath + 'qdrTopology.html'
+      })
+      .when('/list', {
+        templateUrl: QDRTemplatePath + 'qdrList.html'
+      })
+      .when('/schema', {
+        templateUrl: QDRTemplatePath + 'qdrSchema.html'
+      })
+      .when('/charts', {
+        templateUrl: QDRTemplatePath + 'qdrCharts.html'
+      })
+      .when('/chord', {
+        templateUrl: QDRTemplatePath + 'qdrChord.html'
+      })
+      .when('/connect', {
+        templateUrl: QDRTemplatePath + 'qdrConnect.html'
+      });
+  });
+
+  QDR.module.config(function ($compileProvider) {
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension|file|blob):/);
+    $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/);
+  });
+
+  QDR.module.filter('to_trusted', ['$sce', function($sce){
+    return function(text) {
+      return $sce.trustAsHtml(text);
+    };
+  }]);
+
+  QDR.module.filter('humanify', ['QDRService', function (QDRService) {
+    return function (input) {
+      return QDRService.utilities.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.module.filter('pretty', function () {
+    return function (str) {
+      var formatComma = d3.format(',');
+      if (!isNaN(parseFloat(str)) && isFinite(str))
+        return formatComma(str);
+      return str;
+    };
+  });
+
+  // one-time initialization happens in the run function
+  // of our module
+  QDR.module.run( ['$rootScope', '$route', '$timeout', '$location', '$log', 'QDRService', 'QDRChartService',  function ($rootScope, $route, $timeout, $location, $log, QDRService, QDRChartService) {
+    let QDRLog = new QDRLogger($log, 'main');
+    QDRLog.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);
+    }
+    QDR.queue = d3.queue;
+
+    if (!QDRService.management.connection.is_connected()) {
+      // attempt to connect to the host:port that served this page
+      var host = $location.host();
+      var port = $location.port();
+      var search = $location.search();
+      if (search.org) {
+        if (search.org === 'connect')
+          $location.search('org', 'overview');
+      }
+      var connectOptions = {address: host, port: port};
+      QDRLog.info('Attempting AMQP over websockets connection using address:port of browser ('+host+':'+port+')');
+      QDRService.management.connection.testConnect(connectOptions)
+        .then( function () {
+          // We didn't connect with reconnect: true flag.
+          // The reason being that if we used reconnect:true and the connection failed, rhea would keep trying. There
+          // doesn't appear to be a way to tell it to stop trying to reconnect.
+          QDRService.disconnect();
+          QDRLog.info('Connect succeeded. Using address:port of browser');
+          connectOptions.reconnect = true;
+          // complete the connection (create the sender/receiver)
+          QDRService.connect(connectOptions)
+            .then( function () {
+            // register a callback for when the node list is available (needed for loading saved charts)
+              QDRService.management.topology.addUpdatedAction('initChartService', function() {
+                QDRService.management.topology.delUpdatedAction('initChartService');
+                QDRChartService.init(); // initialize charting service after we are connected
+              });
+              // get the list of nodes
+              QDRService.management.topology.startUpdating(false);
+            });
+        }, function () {
+          QDRLog.info('failed to auto-connect to ' + host + ':' + port);
+          QDRLog.info('redirecting to connect page');
+          $timeout(function () {
+            $location.path('/connect');
+            $location.search('org', org);
+            $location.replace();
+          });
+        });
+    }
+
+    $rootScope.$on('$routeChangeSuccess', function() {
+      var path = $location.path();
+      if (path !== '/connect') {
+        localStorage[QDR_LAST_LOCATION] = path;
+      }
+    });
+  }]);
+
+  QDR.module.controller ('QDR.MainController', ['$scope', '$log', '$location', function ($scope, $log, $location) {
+    let QDRLog = new QDRLogger($log, 'MainController');
+    QDRLog.debug('started QDR.MainController with location.url: ' + $location.url());
+    QDRLog.debug('started QDR.MainController with window.location.pathname : ' + window.location.pathname);
+    $scope.topLevelTabs = [];
+    $scope.topLevelTabs.push({
+      id: 'qdr',
+      content: 'Qpid Dispatch Router Console',
+      title: 'Dispatch Router Console',
+      isValid: function() { return true; },
+      href: function() { return '#connect'; },
+      isActive: function() { return true; }
+    });
+  }]);
+
+  QDR.module.controller ('QDR.Core', function ($scope, $rootScope) {
+    $scope.alerts = [];
+    $scope.breadcrumb = {};
+    $scope.closeAlert = function(index) {
+      $scope.alerts.splice(index, 1);
+    };
+    $scope.$on('setCrumb', function(event, data) {
+      $scope.breadcrumb = data;
+    });
+    $scope.$on('newAlert', function(event, data) {
+      $scope.alerts.push(data);
+      $scope.$apply();
+    });
+    $scope.$on('clearAlerts', function () {
+      $scope.alerts = [];
+      $scope.$apply();
+    });
+    $scope.pageMenuClicked = function () {
+      $rootScope.$broadcast('pageMenuClicked');
+    };
+  });
+
+  QDR.module.controller('QDR.NavBarController', NavBarController);
+  QDR.module.controller('QDR.OverviewController', OverviewController);
+  QDR.module.controller('QDR.OverviewChartsController', OverviewChartsController);
+  QDR.module.controller('QDR.OverviewLogsController', OverviewLogsController);
+  QDR.module.controller('QDR.TopAddressesController', TopAddressesController);
+  QDR.module.controller('QDR.ChartDialogController', ChartDialogController);
+  QDR.module.controller('QDR.SettingsController', SettingsController);
+  QDR.module.controller('QDR.TopologyController', TopologyController);
+  QDR.module.controller('QDR.ChordController', ChordController);
+  QDR.module.controller('QDR.ListController', ListController);
+  QDR.module.controller('QDR.SchemaController', SchemaController);
+  QDR.module.controller('QDR.ChartsController', ChartsController);
+  
+  QDR.module.service('QDRService', QDRService);
+  QDR.module.service('QDRChartService', QDRChartService);
+  QDR.module.directive('posint', posint);
+  //  .directive('exampleDirective', () => new ExampleDirective);
+}({}));
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/modules/connection.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/modules/connection.js b/console/stand-alone/modules/connection.js
new file mode 100644
index 0000000..db21e01
--- /dev/null
+++ b/console/stand-alone/modules/connection.js
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2017 Red Hat Inc.
+ *
+ * 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.
+ */
+/* global Promise */
+
+const rhea = require('rhea');
+//import { on, websocket_connect, removeListener, once, connect } from 'rhea';
+import Correlator from './correlator.js';
+
+class ConnectionManager {
+  constructor(protocol) {
+    this.sender = undefined;
+    this.receiver = undefined;
+    this.connection = undefined;
+    this.version = undefined;
+    this.errorText = undefined;
+    this.protocol = protocol;
+    this.schema = undefined;
+    this.connectActions = [];
+    this.disconnectActions = [];
+    this.correlator = new Correlator();
+    this.on_message = (function (context) {
+      this.correlator.resolve(context);
+    }).bind(this);
+    this.on_disconnected = (function () {
+      this.errorText = 'Disconnected';
+      this.executeDisconnectActions(this.errorText);
+    }).bind(this);
+    this.on_connection_open = (function () {
+      this.executeConnectActions();
+    }).bind(this);
+  }
+  versionCheck(minVer) {
+    var verparts = this.version.split('.');
+    var minparts = minVer.split('.');
+    try {
+      for (var i = 0; i < minparts.length; ++i) {
+        if (parseInt(minVer[i] > parseInt(verparts[i])))
+          return false;
+      }
+    }
+    catch (e) {
+      return false;
+    }
+    return true;
+  }
+  addConnectAction(action) {
+    if (typeof action === 'function') {
+      this.delConnectAction(action);
+      this.connectActions.push(action);
+    }
+  }
+  addDisconnectAction(action) {
+    if (typeof action === 'function') {
+      this.delDisconnectAction(action);
+      this.disconnectActions.push(action);
+    }
+  }
+  delConnectAction(action) {
+    if (typeof action === 'function') {
+      var index = this.connectActions.indexOf(action);
+      if (index >= 0)
+        this.connectActions.splice(index, 1);
+    }
+  }
+  delDisconnectAction(action) {
+    if (typeof action === 'function') {
+      var index = this.disconnectActions.indexOf(action);
+      if (index >= 0)
+        this.disconnectActions.splice(index, 1);
+    }
+  }
+  executeConnectActions() {
+    this.connectActions.forEach(function (action) {
+      try {
+        action();
+      }
+      catch (e) {
+        // in case the page that registered the handler has been unloaded
+      }
+    });
+    this.connectActions = [];
+  }
+  executeDisconnectActions(message) {
+    this.disconnectActions.forEach(function (action) {
+      try {
+        action(message);
+      }
+      catch (e) {
+        // in case the page that registered the handler has been unloaded
+      }
+    });
+    this.disconnectActions = [];
+  }
+  on(eventType, fn) {
+    if (eventType === 'connected') {
+      this.addConnectAction(fn);
+    }
+    else if (eventType === 'disconnected') {
+      this.addDisconnectAction(fn);
+    }
+    else {
+      console.log('unknown event type ' + eventType);
+    }
+  }
+  setSchema(schema) {
+    this.schema = schema;
+  }
+  is_connected() {
+    return this.connection &&
+      this.sender &&
+      this.receiver &&
+      this.receiver.remote &&
+      this.receiver.remote.attach &&
+      this.receiver.remote.attach.source &&
+      this.receiver.remote.attach.source.address &&
+      this.connection.is_open();
+  }
+  disconnect() {
+    if (this.sender)
+      this.sender.close();
+    if (this.receiver)
+      this.receiver.close();
+    if (this.connection)
+      this.connection.close();
+  }
+  createSenderReceiver(options) {
+    return new Promise((function (resolve, reject) {
+      var timeout = options.timeout || 10000;
+      // set a timer in case the setup takes too long
+      var giveUp = (function () {
+        this.connection.removeListener('receiver_open', receiver_open);
+        this.connection.removeListener('sendable', sendable);
+        this.errorText = 'timed out creating senders and receivers';
+        reject(Error(this.errorText));
+      }).bind(this);
+      var timer = setTimeout(giveUp, timeout);
+      // register an event hander for when the setup is complete
+      var sendable = (function (context) {
+        clearTimeout(timer);
+        this.version = this.connection.properties ? this.connection.properties.version : '0.1.0';
+        // in case this connection dies
+        rhea.on('disconnected', this.on_disconnected);
+        // in case this connection dies and is then reconnected automatically
+        rhea.on('connection_open', this.on_connection_open);
+        // receive messages here
+        this.connection.on('message', this.on_message);
+        resolve(context);
+      }).bind(this);
+      this.connection.once('sendable', sendable);
+      // Now actually createt the sender and receiver.
+      // register an event handler for when the receiver opens
+      var receiver_open = (function () {
+        // once the receiver is open, create the sender
+        if (options.sender_address)
+          this.sender = this.connection.open_sender(options.sender_address);
+        else
+          this.sender = this.connection.open_sender();
+      }).bind(this);
+      this.connection.once('receiver_open', receiver_open);
+      // create a dynamic receiver
+      this.receiver = this.connection.open_receiver({ source: { dynamic: true } });
+    }).bind(this));
+  }
+  connect(options) {
+    return new Promise((function (resolve, reject) {
+      var finishConnecting = function () {
+        this.createSenderReceiver(options)
+          .then(function (results) {
+            resolve(results);
+          }, function (error) {
+            reject(error);
+          });
+      };
+      if (!this.connection) {
+        options.test = false; // if you didn't want a connection, you should have called testConnect() and not connect()
+        this.testConnect(options)
+          .then((function () {
+            finishConnecting.call(this);
+          }).bind(this), (function () {
+            // connect failed or timed out
+            this.errorText = 'Unable to connect';
+            this.executeDisconnectActions(this.errorText);
+            reject(Error(this.errorText));
+          }).bind(this));
+      }
+      else {
+        finishConnecting.call(this);
+      }
+    }).bind(this));
+  }
+  getReceiverAddress() {
+    return this.receiver.remote.attach.source.address;
+  }
+  // Try to connect using the options.
+  // if options.test === true -> close the connection if it succeeded and resolve the promise
+  // if the connection attempt fails or times out, reject the promise regardless of options.test
+  testConnect(options, callback) {
+    return new Promise((function (resolve, reject) {
+      var timeout = options.timeout || 10000;
+      var reconnect = options.reconnect || false; // in case options.reconnect is undefined
+      var baseAddress = options.address + ':' + options.port;
+      if (options.linkRouteAddress) {
+        baseAddress += ('/' + options.linkRouteAddress);
+      }
+      var wsprotocol = location.protocol === 'https:' ? 'wss' : 'ws';
+      if (this.connection) {
+        delete this.connection;
+        this.connection = null;
+      }
+      var ws = rhea.websocket_connect(WebSocket);
+      var c = {
+        connection_details: new ws(wsprotocol + '://' + baseAddress, ['binary']),
+        reconnect: reconnect,
+        properties: options.properties || { console_identifier: 'Dispatch console' }
+      };
+      if (options.hostname)
+        c.hostname = options.hostname;
+      if (options.username && options.username !== '') {
+        c.username = options.username;
+      }
+      if (options.password && options.password !== '') {
+        c.password = options.password;
+      }
+      // set a timeout
+      var disconnected = (function () {
+        clearTimeout(timer);
+        rhea.removeListener('disconnected', disconnected);
+        rhea.removeListener('connection_open', connection_open);
+        this.connection = null;
+        var rej = 'failed to connect';
+        if (callback)
+          callback({ error: rej });
+        reject(Error(rej));
+      }).bind(this);
+      var timer = setTimeout(disconnected, timeout);
+      // the event handler for when the connection opens
+      var connection_open = (function (context) {
+        clearTimeout(timer);
+        // prevent future disconnects from calling reject
+        rhea.removeListener('disconnected', disconnected);
+        // we were just checking. we don't really want a connection
+        if (options.test) {
+          context.connection.close();
+          this.connection = null;
+        }
+        else
+          this.on_connection_open();
+        var res = { context: context };
+        if (callback)
+          callback(res);
+        resolve(res);
+      }).bind(this);
+      // register an event handler for when the connection opens
+      rhea.once('connection_open', connection_open);
+      // register an event handler for if the connection fails to open
+      rhea.once('disconnected', disconnected);
+      // attempt the connection
+      this.connection = rhea.connect(c);
+    }).bind(this));
+  }
+  sendMgmtQuery(operation) {
+    return this.send([], '/$management', operation);
+  }
+  sendQuery(toAddr, entity, attrs, operation) {
+    operation = operation || 'QUERY';
+    var fullAddr = this._fullAddr(toAddr);
+    var body = { attributeNames: attrs || [] };
+    return this.send(body, fullAddr, operation, this.schema.entityTypes[entity].fullyQualifiedType);
+  }
+  send(body, to, operation, entityType) {
+    var application_properties = {
+      operation: operation,
+      type: 'org.amqp.management',
+      name: 'self'
+    };
+    if (entityType)
+      application_properties.entityType = entityType;
+    return this._send(body, to, application_properties);
+  }
+  sendMethod(toAddr, entity, attrs, operation, props) {
+    var fullAddr = this._fullAddr(toAddr);
+    var application_properties = {
+      operation: operation,
+    };
+    if (entity) {
+      application_properties.type = this.schema.entityTypes[entity].fullyQualifiedType;
+    }
+    if (attrs.name)
+      application_properties.name = attrs.name;
+    else if (attrs.identity)
+      application_properties.identity = attrs.identity;
+    if (props) {
+      for (var attrname in props) {
+        application_properties[attrname] = props[attrname];
+      }
+    }
+    return this._send(attrs, fullAddr, application_properties);
+  }
+  _send(body, to, application_properties) {
+    var _correlationId = this.correlator.corr();
+    var self = this;
+    return new Promise(function (resolve, reject) {
+      self.correlator.register(_correlationId, resolve, reject);
+      self.sender.send({
+        body: body,
+        to: to,
+        reply_to: self.receiver.remote.attach.source.address,
+        correlation_id: _correlationId,
+        application_properties: application_properties
+      });
+    });
+  }
+  _fullAddr(toAddr) {
+    var toAddrParts = toAddr.split('/');
+    toAddrParts.shift();
+    var fullAddr = toAddrParts.join('/');
+    return fullAddr;
+  }
+  availableQeueuDepth() {
+    return this.correlator.depth();
+  }
+}
+
+class ConnectionException {
+  constructor(message) {
+    this.message = message;
+    this.name = 'ConnectionException';
+  }
+}
+
+const _ConnectionManager = ConnectionManager;
+export { _ConnectionManager as ConnectionManager };
+const _ConnectionException = ConnectionException;
+export { _ConnectionException as ConnectionException };

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/modules/correlator.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/modules/correlator.js b/console/stand-alone/modules/correlator.js
new file mode 100644
index 0000000..bf34f93
--- /dev/null
+++ b/console/stand-alone/modules/correlator.js
@@ -0,0 +1,50 @@
+/*
+  * Copyright 2017 Red Hat Inc.
+  *
+  * 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.
+  */
+
+import { utils } from './utilities.js';
+
+class Correlator {
+  constructor() {
+    this._objects = {};
+    this._correlationID = 0;
+    this.maxCorrelatorDepth = 10;
+  }
+  corr() {
+    return ++(this._correlationID) + '';
+  }
+  // Associate this correlation id with the promise's resolve and reject methods
+  register(id, resolve, reject) {
+    this._objects[id] = { resolver: resolve, rejector: reject };
+  }
+  // Call the promise's resolve method.
+  // This is called by rhea's receiver.on('message') function
+  resolve(context) {
+    var correlationID = context.message.correlation_id;
+    // call the promise's resolve function with a copy of the rhea response (so we don't keep any references to internal rhea data)
+    this._objects[correlationID].resolver({ response: utils.copy(context.message.body), context: context });
+    delete this._objects[correlationID];
+  }
+  reject(id, error) {
+    this._objects[id].rejector(error);
+    delete this._objects[id];
+  }
+  // Return the number of requests that can be sent before we start queuing requests
+  depth() {
+    return Math.max(1, this.maxCorrelatorDepth - Object.keys(this._objects).length);
+  }
+}
+
+export default Correlator;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/modules/management.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/modules/management.js b/console/stand-alone/modules/management.js
new file mode 100644
index 0000000..4b3bb32
--- /dev/null
+++ b/console/stand-alone/modules/management.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * 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.
+ */
+
+/* global Promise */
+
+import { ConnectionManager } from './connection.js';
+import Topology from './topology.js';
+
+export class Management {
+  constructor(protocol) {
+    this.connection = new ConnectionManager(protocol);
+    this.topology = new Topology(this.connection);
+  }
+  getSchema(callback) {
+    var self = this;
+    return new Promise(function (resolve, reject) {
+      self.connection.sendMgmtQuery('GET-SCHEMA')
+        .then(function (responseAndContext) {
+          var response = responseAndContext.response;
+          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.connection.setSchema(response);
+          if (callback)
+            callback(response);
+          resolve(response);
+        }, function (error) {
+          if (callback)
+            callback(error);
+          reject(error);
+        });
+    });
+  }
+  schema() {
+    return this.connection.schema;
+  }
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/modules/topology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/modules/topology.js b/console/stand-alone/modules/topology.js
new file mode 100644
index 0000000..e208a6f
--- /dev/null
+++ b/console/stand-alone/modules/topology.js
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * 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.
+ */
+
+/* global Promise d3 */
+
+import { utils } from './utilities.js';
+
+class Topology {
+  constructor(connectionManager) {
+    this.connection = connectionManager;
+    this.updatedActions = {};
+    this.entities = []; // which entities to request each topology update
+    this.entityAttribs = {};
+    this._nodeInfo = {}; // info about all known nodes and entities
+    this.filtering = false; // filter out nodes that don't have connection info
+    this.timeout = 5000;
+    this.updateInterval = 5000;
+    this._getTimer = null;
+    this.updating = false;
+  }
+  addUpdatedAction(key, action) {
+    if (typeof action === 'function') {
+      this.updatedActions[key] = action;
+    }
+  }
+  delUpdatedAction(key) {
+    if (key in this.updatedActions)
+      delete this.updatedActions[key];
+  }
+  executeUpdatedActions(error) {
+    for (var action in this.updatedActions) {
+      this.updatedActions[action].apply(this, [error]);
+    }
+  }
+  setUpdateEntities(entities) {
+    this.entities = entities;
+    for (var i = 0; i < entities.length; i++) {
+      this.entityAttribs[entities[i]] = [];
+    }
+  }
+  addUpdateEntities(entityAttribs) {
+    if (Object.prototype.toString.call(entityAttribs) !== '[object Array]') {
+      entityAttribs = [entityAttribs];
+    }
+    for (var i = 0; i < entityAttribs.length; i++) {
+      var entity = entityAttribs[i].entity;
+      this.entityAttribs[entity] = entityAttribs[i].attrs || [];
+    }
+  }
+  on(eventName, fn, key) {
+    if (eventName === 'updated')
+      this.addUpdatedAction(key, fn);
+  }
+  unregister(eventName, key) {
+    if (eventName === 'updated')
+      this.delUpdatedAction(key);
+  }
+  nodeInfo() {
+    return this._nodeInfo;
+  }
+  get() {
+    return new Promise((function (resolve, reject) {
+      this.connection.sendMgmtQuery('GET-MGMT-NODES')
+        .then((function (response) {
+          response = response.response;
+          if (Object.prototype.toString.call(response) === '[object Array]') {
+            var workInfo = {};
+            // if there is only one node, it will not be returned
+            if (response.length === 0) {
+              var parts = this.connection.getReceiverAddress().split('/');
+              parts[parts.length - 1] = '$management';
+              response.push(parts.join('/'));
+            }
+            for (var i = 0; i < response.length; ++i) {
+              workInfo[response[i]] = {};
+            }
+            var gotResponse = function (nodeName, entity, response) {
+              workInfo[nodeName][entity] = response;
+            };
+            var q = d3.queue(this.connection.availableQeueuDepth());
+            for (var id in workInfo) {
+              for (var entity in this.entityAttribs) {
+                q.defer((this.q_fetchNodeInfo).bind(this), id, entity, this.entityAttribs[entity], q, gotResponse);
+              }
+            }
+            q.await((function () {
+              // filter out nodes that have no connection info
+              if (this.filtering) {
+                for (var id in workInfo) {
+                  if (!(workInfo[id].connection)) {
+                    this.flux = true;
+                    delete workInfo[id];
+                  }
+                }
+              }
+              this._nodeInfo = utils.copy(workInfo);
+              this.onDone(this._nodeInfo);
+              resolve(this._nodeInfo);
+            }).bind(this));
+          }
+        }).bind(this), function (error) {
+          reject(error);
+        });
+    }).bind(this));
+  }
+  onDone(result) {
+    clearTimeout(this._getTimer);
+    if (this.updating)
+      this._getTimer = setTimeout((this.get).bind(this), this.updateInterval);
+    this.executeUpdatedActions(result);
+  }
+  startUpdating(filter) {
+    this.stopUpdating();
+    this.updating = true;
+    this.filtering = filter;
+    this.get();
+  }
+  stopUpdating() {
+    this.updating = false;
+    if (this._getTimer) {
+      clearTimeout(this._getTimer);
+      this._getTimer = null;
+    }
+  }
+  fetchEntity(node, entity, attrs, callback) {
+    var results = {};
+    var gotResponse = function (nodeName, dotentity, response) {
+      results = response;
+    };
+    var q = d3.queue(this.connection.availableQeueuDepth());
+    q.defer((this.q_fetchNodeInfo).bind(this), node, entity, attrs, q, gotResponse);
+    q.await(function () {
+      callback(node, entity, results);
+    });
+  }
+  // called from d3.queue.defer so the last argument (callback) is supplied by d3
+  q_fetchNodeInfo(nodeId, entity, attrs, q, heartbeat, callback) {
+    this.getNodeInfo(nodeId, entity, attrs, q, function (nodeName, dotentity, response) {
+      heartbeat(nodeName, dotentity, response);
+      callback(null);
+    });
+  }
+  // get all the requested entities/attributes for a single router
+  fetchEntities(node, entityAttribs, doneCallback, resultCallback) {
+    var q = d3.queue(this.connection.availableQeueuDepth());
+    var results = {};
+    if (!resultCallback) {
+      resultCallback = function (nodeName, dotentity, response) {
+        if (!results[nodeName])
+          results[nodeName] = {};
+        results[nodeName][dotentity] = response;
+      };
+    }
+    var gotAResponse = function (nodeName, dotentity, response) {
+      resultCallback(nodeName, dotentity, response);
+    };
+    if (Object.prototype.toString.call(entityAttribs) !== '[object Array]') {
+      entityAttribs = [entityAttribs];
+    }
+    for (var i = 0; i < entityAttribs.length; ++i) {
+      var ea = entityAttribs[i];
+      q.defer((this.q_fetchNodeInfo).bind(this), node, ea.entity, ea.attrs || [], q, gotAResponse);
+    }
+    q.await(function () {
+      doneCallback(results);
+    });
+  }
+  // get all the requested entities for all known routers
+  fetchAllEntities(entityAttribs, doneCallback, resultCallback) {
+    var q = d3.queue(this.connection.availableQeueuDepth());
+    var results = {};
+    if (!resultCallback) {
+      resultCallback = function (nodeName, dotentity, response) {
+        if (!results[nodeName])
+          results[nodeName] = {};
+        results[nodeName][dotentity] = response;
+      };
+    }
+    var gotAResponse = function (nodeName, dotentity, response) {
+      resultCallback(nodeName, dotentity, response);
+    };
+    if (Object.prototype.toString.call(entityAttribs) !== '[object Array]') {
+      entityAttribs = [entityAttribs];
+    }
+    var nodes = Object.keys(this._nodeInfo);
+    for (var n = 0; n < nodes.length; ++n) {
+      for (var i = 0; i < entityAttribs.length; ++i) {
+        var ea = entityAttribs[i];
+        q.defer((this.q_fetchNodeInfo).bind(this), nodes[n], ea.entity, ea.attrs || [], q, gotAResponse);
+      }
+    }
+    q.await(function () {
+      doneCallback(results);
+    });
+  }
+  // enusre all the topology nones have all these entities
+  ensureAllEntities(entityAttribs, callback, extra) {
+    this.ensureEntities(Object.keys(this._nodeInfo), entityAttribs, callback, extra);
+  }
+  // ensure these nodes have all these entities. don't fetch unless forced to
+  ensureEntities(nodes, entityAttribs, callback, extra) {
+    if (Object.prototype.toString.call(entityAttribs) !== '[object Array]') {
+      entityAttribs = [entityAttribs];
+    }
+    if (Object.prototype.toString.call(nodes) !== '[object Array]') {
+      nodes = [nodes];
+    }
+    this.addUpdateEntities(entityAttribs);
+    var q = d3.queue(this.connection.availableQeueuDepth());
+    for (var n = 0; n < nodes.length; ++n) {
+      for (var i = 0; i < entityAttribs.length; ++i) {
+        var ea = entityAttribs[i];
+        // if we don'e already have the entity or we want to force a refresh
+        if (!this._nodeInfo[nodes[n]][ea.entity] || ea.force)
+          q.defer((this.q_ensureNodeInfo).bind(this), nodes[n], ea.entity, ea.attrs || [], q);
+      }
+    }
+    q.await(function () {
+      callback(extra);
+    });
+  }
+  addNodeInfo(id, entity, values) {
+    // save the results in the nodeInfo object
+    if (id) {
+      if (!(id in this._nodeInfo)) {
+        this._nodeInfo[id] = {};
+      }
+      // copy the values to allow garbage collection
+      this._nodeInfo[id][entity] = values;
+    }
+  }
+  isLargeNetwork() {
+    return Object.keys(this._nodeInfo).length >= 12;
+  }
+  getConnForLink(link) {
+    // find the connection for this link
+    var conns = this._nodeInfo[link.nodeId].connection;
+    var connIndex = conns.attributeNames.indexOf('identity');
+    var linkCons = conns.results.filter(function (conn) {
+      return conn[connIndex] === link.connectionId;
+    });
+    return utils.flatten(conns.attributeNames, linkCons[0]);
+  }
+  nodeNameList() {
+    var nl = [];
+    for (var id in this._nodeInfo) {
+      nl.push(utils.nameFromId(id));
+    }
+    return nl.sort();
+  }
+  nodeIdList() {
+    var nl = [];
+    for (var id in this._nodeInfo) {
+      //if (this._nodeInfo['connection'])
+      nl.push(id);
+    }
+    return nl.sort();
+  }
+  nodeList() {
+    var nl = [];
+    for (var id in this._nodeInfo) {
+      nl.push({
+        name: utils.nameFromId(id),
+        id: id
+      });
+    }
+    return nl;
+  }
+  // d3.queue'd function to make a management query for entities/attributes
+  q_ensureNodeInfo(nodeId, entity, attrs, q, callback) {
+    this.getNodeInfo(nodeId, entity, attrs, q, (function (nodeName, dotentity, response) {
+      this.addNodeInfo(nodeName, dotentity, response);
+      callback(null);
+    }).bind(this));
+    return {
+      abort: function () {
+        delete this._nodeInfo[nodeId];
+      }
+    };
+  }
+  getNodeInfo(nodeName, entity, attrs, q, callback) {
+    var timedOut = function (q) {
+      q.abort();
+    };
+    var atimer = setTimeout(timedOut, this.timeout, q);
+    this.connection.sendQuery(nodeName, entity, attrs)
+      .then(function (response) {
+        clearTimeout(atimer);
+        callback(nodeName, entity, response.response);
+      }, function () {
+        q.abort();
+      });
+  }
+  getMultipleNodeInfo(nodeNames, entity, attrs, callback, selectedNodeId, aggregate) {
+    var self = this;
+    if (typeof aggregate === 'undefined')
+      aggregate = true;
+    var responses = {};
+    var gotNodesResult = function (nodeName, dotentity, response) {
+      responses[nodeName] = response;
+    };
+    var q = d3.queue(this.connection.availableQeueuDepth());
+    nodeNames.forEach(function (id) {
+      q.defer((self.q_fetchNodeInfo).bind(self), id, entity, attrs, q, gotNodesResult);
+    });
+    q.await(function () {
+      if (aggregate)
+        self.aggregateNodeInfo(nodeNames, entity, selectedNodeId, responses, callback);
+      else {
+        callback(nodeNames, entity, responses);
+      }
+    });
+  }
+  quiesceLink(nodeId, name) {
+    var attributes = {
+      adminStatus: 'disabled',
+      name: name
+    };
+    return this.connection.sendMethod(nodeId, 'router.link', attributes, 'UPDATE');
+  }
+  aggregateNodeInfo(nodeNames, entity, selectedNodeId, responses, callback) {
+    // aggregate the responses
+    var self = this;
+    var newResponse = {};
+    var thisNode = responses[selectedNodeId];
+    newResponse.attributeNames = thisNode.attributeNames;
+    newResponse.results = thisNode.results;
+    newResponse.aggregates = [];
+    // initialize the aggregates
+    for (var i = 0; i < thisNode.results.length; ++i) {
+      // there is a result for each unique entity found (ie addresses, links, etc.)
+      var result = thisNode.results[i];
+      var vals = [];
+      // there is a val for each attribute in this entity
+      result.forEach(function (val) {
+        vals.push({
+          sum: val,
+          detail: []
+        });
+      });
+      newResponse.aggregates.push(vals);
+    }
+    var nameIndex = thisNode.attributeNames.indexOf('name');
+    var ent = self.connection.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) {
+          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: utils.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: utils.nameFromId(id),
+                val: val
+              }]
+            });
+          });
+          newResponse.aggregates.push(vals);
+        }
+      });
+    });
+    callback(nodeNames, entity, newResponse);
+  }
+}
+
+export default Topology;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/modules/utilities.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/modules/utilities.js b/console/stand-alone/modules/utilities.js
new file mode 100644
index 0000000..328da38
--- /dev/null
+++ b/console/stand-alone/modules/utilities.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015 Red Hat Inc.
+ *
+ * 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.
+ */
+
+/* global d3 */
+var ddd = typeof window === 'undefined' ? require ('d3') : d3;
+
+var utils = {
+  isAConsole: function (properties, connectionId, nodeType, key) {
+    return this.isConsole({
+      properties: properties,
+      connectionId: connectionId,
+      nodeType: nodeType,
+      key: key
+    });
+  },
+  isConsole: function (d) {
+    return (d && d.properties && d.properties.console_identifier === 'Dispatch console');
+  },
+  isArtemis: function (d) {
+    return (d.nodeType === 'route-container' || d.nodeType === 'on-demand') && (d.properties && d.properties.product === 'apache-activemq-artemis');
+  },
+
+  isQpid: function (d) {
+    return (d.nodeType === 'route-container' || d.nodeType === 'on-demand') && (d.properties && d.properties.product === 'qpid-cpp');
+  },
+  flatten: function (attributes, result) {
+    if (!attributes || !result)
+      return {};
+    var flat = {};
+    attributes.forEach(function(attr, i) {
+      if (result && result.length > i)
+        flat[attr] = result[i];
+    });
+    return flat;
+  },
+  copy: function (obj) {
+    if (obj)
+      return JSON.parse(JSON.stringify(obj));
+  },
+  identity_clean: function (identity) {
+    if (!identity)
+      return '-';
+    var pos = identity.indexOf('/');
+    if (pos >= 0)
+      return identity.substring(pos + 1);
+    return identity;
+  },
+  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] == 'E') return 'link-incoming';
+    if (addr[0] == 'D') return 'link-outgoing';
+    if (addr[0] == 'F') return 'link-outgoing';
+    if (addr[0] == 'T') return 'topo';
+    return 'unknown: ' + addr[0];
+  },
+  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 = ddd.format(',');
+    if (!isNaN(parseFloat(v)) && isFinite(v))
+      return formatComma(v);
+    return v;
+  },
+  isMSIE: function () {
+    return (document.documentMode || /Edge/.test(navigator.userAgent));
+  },
+  valFor: function (aAr, vAr, key) {
+    var idx = aAr.indexOf(key);
+    if ((idx > -1) && (idx < vAr.length)) {
+      return vAr[idx];
+    }
+    return null;
+  },
+  // extract the name of the router from the router id
+  nameFromId: function (id) {
+    // the router id looks like 'amqp:/topo/0/routerName/$managemrnt'
+    var parts = id.split('/');
+    // handle cases where the router name contains a /
+    parts.splice(0, 3); // remove amqp, topo, 0
+    parts.pop(); // remove $management
+    return parts.join('/');
+  }
+  
+};
+export { utils };
\ 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