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/08/19 16:59:08 UTC

[01/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master c9d14137d -> d98ae37ef


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/lib/rhea-min.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/lib/rhea-min.js b/console/stand-alone/plugin/lib/rhea-min.js
index f61579a..42ca130 100644
--- a/console/stand-alone/plugin/lib/rhea-min.js
+++ b/console/stand-alone/plugin/lib/rhea-min.js
@@ -1,93 +1,6 @@
-require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-(function (process,Buffer){
-"use strict";function get_socket_id(t){return t.get_id_string?t.get_id_string():t.localAddress+":"+t.localPort+" -> "+t.remoteAddress+":"+t.remotePort}function session_per_connection(t){var e=null;return{get_session:function(){return e||(e=t.create_session(),e.begin()),e}}}function restrict(t,e){if(t){var o,n=t;return function(i){return o!==i&&(n=t,o=i),n--?e(i):-1}}return e}function backoff(t,e){var o,n=t;return function(i){o!==i&&(n=t,o=i);var s=n,r=2*n;return n=e>r?r:e,s}}function get_connect_fn(t){if(void 0===t.transport||"tcp"===t.transport)return net.connect;if("tls"===t.transport||"ssl"===t.transport)return tls.connect;throw Error("Unrecognised transport: "+t.transport)}function connection_details(t){var e={};return e.connect=t.connect?t.connect:get_connect_fn(t),e.host=t.host?t.host:"localhost",e.port=t.port?t.port:5672,e.options=t,e}function delegate_to_session(t){Connection.prototype["on_"+t]=function(e){var o=this.remote_channel_map[e.channel];if(!o)throw Error(t+" receiv
 ed on invalid channel "+e.channel);o["on_"+t](e)}}var frames=require("./frames.js"),log=require("./log.js"),sasl=require("./sasl.js"),types=require("./types.js"),util=require("./util.js"),EndpointState=require("./endpoint.js"),Session=require("./session.js"),Transport=require("./transport.js"),net=require("net"),tls=require("tls"),EventEmitter=require("events").EventEmitter,AMQP_PROTOCOL_ID=0,TLS_PROTOCOL_ID=2,conn_counter=1,Connection=function(t,e){if(this.options={},t)for(var o in t)this.options[o]=t[o];if(this.container=e,this.options.id||(this.options.id="connection-"+conn_counter++),this.options.container_id||(this.options.container_id=e?e.id:util.generate_uuid()),!this.options.connection_details){var n=this;this.options.connection_details=function(){return connection_details(n.options)}}var i=this.get_option("reconnect",!0);if("boolean"==typeof i&&i){var s=this.get_option("initial_reconnect_delay",100),r=this.get_option("max_reconnect_delay",6e4);this.options.reconnect=restric
 t(this.get_option("reconnect_limit"),backoff(s,r))}else if("number"==typeof i){var c=this.options.reconnect;this.options.reconnect=restrict(this.get_option("reconnect_limit"),function(){return c})}this.registered=!1,this.state=new EndpointState,this.local_channel_map={},this.remote_channel_map={},this.local={},this.remote={},this.local.open=frames.open(this.options),this.local.close=frames.close({}),this.session_policy=session_per_connection(this),this.amqp_transport=new Transport(this.options.id,AMQP_PROTOCOL_ID,frames.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};Connection.prototype=Object.create(EventEmitter.prototype),Connection.prototype.constructor=Connection,Connection.prototype.dispatch=function(t,e){return log.events("Connection got event: "+t),this.listeners(t).length?(EventEmitter.proto
 type.emit.apply(this,arguments),!0):this.container?this.container.dispatch.apply(this.container,arguments):void 0},Connection.prototype.reset=function(){this.abort_idle&&(this.abort_idle=!1,this.local.close.error=void 0,this.state=new EndpointState,this.state.open()),this.amqp_transport=new Transport(this.options.id,AMQP_PROTOCOL_ID,frames.TYPE_AMQP,this),this.sasl_transport=void 0,this.transport=this.amqp_transport,this.state.disconnected(),this.remote={},this.remote_channel_map={};for(var t in this.local_channel_map)this.local_channel_map[t].reset();this.socket_ready=!1},Connection.prototype.connect=function(){return this.is_server=!1,this._connect(this.options.connection_details(this.conn_established_counter)),this.open(),this},Connection.prototype.reconnect=function(){return this.scheduled_reconnect=void 0,log.reconnect("reconnecting..."),this.reset(),this._connect(this.options.connection_details(this.conn_established_counter)),process.nextTick(this._process.bind(this)),this},Co
 nnection.prototype._connect=function(t){return t.connect?this.init(t.connect(t.port,t.host,t.options,this.connected.bind(this))):this.init(get_connect_fn(t)(t.port,t.host,t.options,this.connected.bind(this))),this},Connection.prototype.accept=function(t){return this.is_server=!0,log.io("["+this.id+"] client accepted: "+get_socket_id(t)),this.socket_ready=!0,this.init(t)},Connection.prototype.init=function(t){if(this.socket=t,this.socket.on("data",this.input.bind(this)),this.socket.on("error",this.error.bind(this)),this.socket.on("end",this.eof.bind(this)),this.is_server){var e;this.container&&Object.getOwnPropertyNames(this.container.sasl_server_mechanisms).length&&(e=this.container.sasl_server_mechanisms),this.socket.encrypted&&this.socket.authorized&&this.get_option("enable_sasl_external",!1)&&(e=sasl.server_add_external(e?util.clone(e):{})),e&&(this.sasl_transport=new sasl.Server(this,e))}else{var o=this.get_option("sasl_mechanisms");if(!o){var n=this.get_option("username"),i=thi
 s.get_option("password");n&&(o=sasl.client_mechanisms(),i?o.enable_plain(n,i):o.enable_anonymous(n))}this.socket.encrypted&&this.options.cert&&this.get_option("enable_sasl_external",!1)&&(o||(o=sasl.client_mechanisms()),o.enable_external()),o&&(this.sasl_transport=new sasl.Client(this,o))}return this.transport=this.sasl_transport?this.sasl_transport:this.amqp_transport,this},Connection.prototype.attach_sender=function(t){return this.session_policy.get_session().attach_sender(t)},Connection.prototype.open_sender=Connection.prototype.attach_sender,Connection.prototype.attach_receiver=function(t){return this.session_policy.get_session().attach_receiver(t)},Connection.prototype.open_receiver=Connection.prototype.attach_receiver,Connection.prototype.get_option=function(t,e){return void 0!==this.options[t]?this.options[t]:this.container?this.container.get_option(t,e):e},Connection.prototype.connected=function(){this.socket_ready=!0,this.conn_established_counter++,log.io("["+this.options.i
 d+"] connected "+get_socket_id(this.socket)),this.output()},Connection.prototype.sasl_failed=function(t){this.transport_error={condition:"amqp:unauthorized-access",description:t},this._handle_error()},Connection.prototype._handle_error=function(){var t=this.get_error();return t?(this.dispatch("connection_error",this._context())||this.dispatch("connection_close",this._context())||console.log("error: "+JSON.stringify(t)),!0):!1},Connection.prototype.get_error=function(){return this.transport_error?this.transport_error:this.remote.close&&this.remote.close.error?this.remote.close.error:void 0},Connection.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)))},Connection.prototype.input=function(t){this.heartbeat_in&&clearTimeout(this.heartbeat_in),log.io("["+this.options.id+"] read "+t.length+" bytes");var e;this.previous_input?(e=Buffer.concat([this.previous_input,t],this.previous_input.length+t.length),this.previous_input=null):e=t;var o=this.transport.read(e,this);o<e.length&&(this.previous_input=e.slice(o)),this.local.open.idle_time_out&&(this.heartbeat_in=setTimeout(this.idle.bind(this),this.local.open.idle_time_out)),this.transport.has_writes_pending()&&this.output()},Connection.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())},Connection.prototype.error=function(t){console.log("["+this.options.id+"] error: "+t),this._disconnected()},Connection.prototype.eof=function(t){this._disconnected()},Connection.prototype._disconnected=function(){if(!this.is_closed()&&vo
 id 0==this.scheduled_reconnect&&(this.dispatch("disconnected",this._context())||console.log("["+this.options.id+"] disconnected"),!this.is_server&&!this.transport_error&&this.options.reconnect)){var t=this.options.reconnect(this.conn_established_counter);t>=0&&(log.reconnect("Scheduled reconnect in "+t+"ms"),this.scheduled_reconnect=setTimeout(this.reconnect.bind(this),t))}},Connection.prototype.open=function(){this.state.open()&&this._register()},Connection.prototype.close=function(){this.state.close()&&this._register()},Connection.prototype.is_open=function(){return this.state.is_open()},Connection.prototype.is_closed=function(){return this.state.is_closed()},Connection.prototype.create_session=function(){for(var t=0;this.local_channel_map[t];)t++;var e=new Session(this,t);return this.local_channel_map[t]=e,e},Connection.prototype.on_open=function(t){if(!this.state.remote_opened())throw Error("Open already received");this.remote.open=t.performative,this.open(),this.dispatch("conne
 ction_open",this._context())},Connection.prototype.on_close=function(t){if(!this.state.remote_closed())throw Error("Close already received");this.remote.close=t.performative,this.close(),this.remote.close.error?this._handle_error():this.dispatch("connection_close",this._context()),this.heartbeat_out&&clearTimeout(this.heartbeat_out)},Connection.prototype._register=function(){this.registered||(this.registered=!0,process.nextTick(this._process.bind(this)))},Connection.prototype._process=function(){this.registered=!1;do{this.state.need_open()&&this._write_open();for(var t in this.local_channel_map)this.local_channel_map[t]._process();this.state.need_close()&&this._write_close()}while(!this.state.has_settled())},Connection.prototype._write_frame=function(t,e,o){this.amqp_transport.encode(frames.amqp_frame(t,e,o)),this.output()},Connection.prototype._write_open=function(){this._write_frame(0,this.local.open.described())},Connection.prototype._write_close=function(){this._write_frame(0,th
 is.local.close.described())},Connection.prototype.on_begin=function(t){var e;if(null===t.performative.remote_channel||void 0===t.performative.remote_channel)e=this.create_session(),e.local.begin.remote_channel=t.channel;else if(e=this.local_channel_map[t.performative.remote_channel],!e)throw Error("Invalid value for remote channel "+t.performative.remote_channel);e.on_begin(t),this.remote_channel_map[t.channel]=e},Connection.prototype.get_peer_certificate=function(){return this.socket&&this.socket.getPeerCertificate?this.socket.getPeerCertificate():void 0},Connection.prototype._context=function(t){var e=t?t:{};return e.connection=this,this.container&&(e.container=this.container),e},delegate_to_session("end"),delegate_to_session("attach"),delegate_to_session("detach"),delegate_to_session("transfer"),delegate_to_session("disposition"),delegate_to_session("flow"),module.exports=Connection;
-}).call(this,require('_process'),require("buffer").Buffer)
-
-},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./sasl.js":8,"./session.js":9,"./transport.js":11,"./types.js":12,"./util.js":13,"_process":24,"buffer":19,"events":23,"net":18,"tls":18}],2:[function(require,module,exports){
-"use strict";var EndpointState=function(){this.init()};EndpointState.prototype.init=function(){this.local_open=!1,this.remote_open=!1,this.open_requests=0,this.close_requests=0,this.initialised=!1},EndpointState.prototype.open=function(){return this.initialised=!0,this.local_open?!1:(this.local_open=!0,this.open_requests++,!0)},EndpointState.prototype.close=function(){return this.local_open?(this.local_open=!1,this.close_requests++,!0):!1},EndpointState.prototype.disconnected=function(){var t=this.local_open;this.init(),t?this.open():this.close()},EndpointState.prototype.remote_opened=function(t){return this.remote_open?!1:(this.remote_open=!0,!0)},EndpointState.prototype.remote_closed=function(t){return this.remote_open?(this.remote_open=!1,!0):!1},EndpointState.prototype.is_open=function(){return this.local_open&&this.remote_open},EndpointState.prototype.is_closed=function(){return this.initialised&&!this.local_open&&!this.remote_open},EndpointState.prototype.has_settled=function(
 ){return 0==this.open_requests&&0==this.close_requests},EndpointState.prototype.need_open=function(){return this.open_requests>0?(this.open_requests--,!0):!1},EndpointState.prototype.need_close=function(){return this.close_requests>0?(this.close_requests--,!0):!1},module.exports=EndpointState;
-},{}],3:[function(require,module,exports){
-"use strict";function define_frame(e,a){var t=types.define_composite(a);frames[a.name]=t.create,by_descriptor[Number(t.descriptor.numeric).toString(10)]=t,by_descriptor[t.descriptor.symbolic]=t}var types=require("./types.js"),frames={},by_descriptor={};frames.read_header=function(e){var a=4,t={},n=e.toString("ascii",0,a);if("AMQP"!==n)throw Error("Invalid protocol header for AMQP "+n);if(t.protocol_id=e.readUInt8(a++),t.major=e.readUInt8(a++),t.minor=e.readUInt8(a++),t.revision=e.readUInt8(a++),1!==t.major||0!==t.minor)throw Error("Unsupported AMQP version: "+JSON.stringify(t));return t},frames.write_header=function(e,a){var t=4;return e.write("AMQP",0,t,"ascii"),e.writeUInt8(a.protocol_id,t++),e.writeUInt8(a.major,t++),e.writeUInt8(a.minor,t++),e.writeUInt8(a.revision,t++),8},frames.TYPE_AMQP=0,frames.TYPE_SASL=1,frames.read_frame=function(e){var a=new types.Reader(e),t={};if(t.size=a.read_uint(4),a.remaining<t.size)return null;var n=a.read_uint(1);if(2>n)throw Error("Invalid data 
 offset, must be at least 2 was "+n);if(t.type=a.read_uint(1),t.type===frames.TYPE_AMQP)t.channel=a.read_uint(2);else{if(t.type!==frames.TYPE_SASL)throw Error("Unknown frame type "+t.type);a.skip(2)}if(n>1&&a.skip(4*n-8),a.remaining()){t.performative=a.read();var r=by_descriptor[t.performative.descriptor.value];r&&(t.performative=new r(t.performative.value)),a.remaining()&&(t.payload=a.read_bytes(a.remaining()))}return t},frames.write_frame=function(e){var a=new types.Writer;if(a.skip(4),a.write_uint(2,1),a.write_uint(e.type,1),e.type===frames.TYPE_AMQP)a.write_uint(e.channel,2);else{if(e.type!==frames.TYPE_SASL)throw Error("Unknown frame type "+e.type);a.write_uint(0,2)}e.performative&&(a.write(e.performative),e.payload&&a.write_bytes(e.payload));var t=a.toBuffer();return t.writeUInt32BE(t.length,0),t},frames.amqp_frame=function(e,a,t){return{channel:e||0,type:frames.TYPE_AMQP,performative:a,payload:t}},frames.sasl_frame=function(e){return{channel:0,type:frames.TYPE_SASL,performativ
 e:e}};var open={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"}]},begin={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"}]},attach={name:"attach",cod
 e: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"}]},flow={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:"bo
 olean",default_value:!1},{name:"echo",type:"boolean",default_value:!1},{name:"properties",type:"symbolic_map"}]},transfer={name:"transfer",code: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}]},disposition={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}]},detach={name:"detach",code:22,fields:[{name:"handle",type:"uint",mandatory:!0},{name:"closed",type:"boolean",de
 fault_value:!1},{name:"error",type:"error"}]},end={name:"end",code:23,fields:[{name:"error",type:"error"}]},close={name:"close",code:24,fields:[{name:"error",type:"error"}]};define_frame(frames.TYPE_AMQP,open),define_frame(frames.TYPE_AMQP,begin),define_frame(frames.TYPE_AMQP,attach),define_frame(frames.TYPE_AMQP,flow),define_frame(frames.TYPE_AMQP,transfer),define_frame(frames.TYPE_AMQP,disposition),define_frame(frames.TYPE_AMQP,detach),define_frame(frames.TYPE_AMQP,end),define_frame(frames.TYPE_AMQP,close);var sasl_mechanisms={name:"sasl_mechanisms",code:64,fields:[{name:"sasl_server_mechanisms",type:"symbol",multiple:!0,mandatory:!0}]},sasl_init={name:"sasl_init",code:65,fields:[{name:"mechanism",type:"symbol",mandatory:!0},{name:"initial_response",type:"binary"},{name:"hostname",type:"string"}]},sasl_challenge={name:"sasl_challenge",code:66,fields:[{name:"challenge",type:"binary",mandatory:!0}]},sasl_response={name:"sasl_response",code:67,fields:[{name:"response",type:"binary",m
 andatory:!0}]},sasl_outcome={name:"sasl_outcome",code:68,fields:[{name:"code",type:"ubyte",mandatory:!0},{name:"additional_data",type:"binary"}]};define_frame(frames.TYPE_SASL,sasl_mechanisms),define_frame(frames.TYPE_SASL,sasl_init),define_frame(frames.TYPE_SASL,sasl_challenge),define_frame(frames.TYPE_SASL,sasl_response),define_frame(frames.TYPE_SASL,sasl_outcome),module.exports=frames;
-},{"./types.js":12}],4:[function(require,module,exports){
-"use strict";function auto_settle(e){e.delivery.settled=!0}function auto_accept(e){e.delivery.update(!0,message.accepted().described())}function is_internal(e){switch(e){case"handle":case"role":case"initial_delivery_count":return!0;default:return!1}}var frames=require("./frames.js"),log=require("./log.js"),message=require("./message.js"),terminus=require("./terminus.js"),types=require("./types.js"),EndpointState=require("./endpoint.js"),FlowController=function(e){this.window=e};FlowController.prototype.update=function(e){var t=this.window-e.receiver.credit;e.receiver.flow(t)};var EventEmitter=require("events").EventEmitter,link=Object.create(EventEmitter.prototype);link.dispatch=function(e,t){log.events("Link got event: "+e),EventEmitter.prototype.emit.apply(this.observers,arguments),this.listeners(e).length?EventEmitter.prototype.emit.apply(this,arguments):this.session.dispatch.apply(this.session,arguments)},link.set_source=function(e){this.local.attach.source=terminus.source(e).de
 scribed()},link.set_target=function(e){this.local.attach.target=terminus.target(e).described()},link.attach=function(){this.state.open()&&this.connection._register()},link.open=link.attach,link.detach=function(){this.local.detach.closed=!1,this.state.close()&&this.connection._register()},link.close=function(){this.local.detach.closed=!0,this.state.close()&&this.connection._register()},link.is_open=function(){return this.session.is_open()&&this.state.is_open()},link.is_closed=function(){return this.session.is_closed()||this.state.is_closed()},link._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())},link.on_attach=function(e){if(!this.state.remote_opened())throw Error("Attach already received");this.remote.handle||(this.remote.handle=e.handle),e.performative.sourc
 e=terminus.unwrap(e.performative.source),e.performative.target=terminus.unwrap(e.performative.target),this.remote.attach=e.performative,this.open(),this.dispatch(this.is_receiver()?"receiver_open":"sender_open",this._context())},link.on_detach=function(e){if(!this.state.remote_closed())throw Error("Detach already received");this.remote.detach=e.performative,this.close(),this.dispatch(this.local.attach.role?"receiver_close":"sender_close",this._context())},link.init=function(e,t,i,s,n){this.session=e,this.connection=e.connection,this.name=t,this.options=void 0===s?{}:s,this.state=new EndpointState,this.issue_flow=!1,this.local={handle:i},this.local.attach=frames.attach({handle:i,name:t,role:n});for(var o in this.local.attach)is_internal(o)||void 0===this.options[o]||(this.local.attach[o]=this.options[o]);this.local.detach=frames.detach({handle:i,closed:!0}),this.remote={handle:void 0},this.delivery_count=0,this.credit=0,this.observers=new EventEmitter},link.reset=function(){this.stat
 e.disconnected(),this.remote={handle:void 0},this.delivery_count=0,this.credit=0},link.has_credit=function(){return this.credit>0},link.is_receiver=function(){return this.local.attach.role},link._context=function(e){var t=e?e:{};return this.is_receiver()?t.receiver=this:t.sender=this,this.session._context(t)},link.get_option=function(e,t){return void 0!==this.options[e]?this.options[e]:this.session.get_option(e,t)};var Sender=function(e,t,i,s){this.init(e,t,i,s,!1),this.local.attach.initial_delivery_count=0,this.tag=0,this.get_option("autosettle",!0)&&this.observers.on("settled",auto_settle)};Sender.prototype=Object.create(link),Sender.prototype.constructor=Sender,Sender.prototype.next_tag=function(){return new String(this.tag++)},Sender.prototype.sendable=function(e){return this.credit&&this.session.outgoing.available()},Sender.prototype.on_flow=function(e){var t=e.performative;this.credit=t.delivery_count+t.link_credit-this.delivery_count,this.is_open()&&(this.dispatch("sender_flo
 w",this._context()),this.sendable()&&this.dispatch("sendable",this._context()))},Sender.prototype.on_transfer=function(e){throw Error("got transfer on sending link")},Sender.prototype.send=function(e,t){return this.session.send(this,t?t:this.next_tag(),message.encode(e),0)};var Receiver=function(e,t,i,s){this.init(e,t,i,s,!0),this.set_prefetch(this.get_option("prefetch",100)),this.get_option("autoaccept",!0)&&this.observers.on("message",auto_accept)};Receiver.prototype=Object.create(link),Receiver.prototype.constructor=Receiver,Receiver.prototype.on_flow=function(e){this.dispatch("receiver_flow",this._context())},Receiver.prototype.flow=function(e){e>0&&(this.credit+=e,this.issue_flow=!0,this.connection._register())},Receiver.prototype.set_prefetch=function(e){if(e>0){var t=new FlowController(e),i=t.update.bind(t);this.observers.on("message",i),this.observers.on("receiver_open",i)}},module.exports={Sender:Sender,Receiver:Receiver};
-},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./message.js":6,"./terminus.js":10,"./types.js":12,"events":23}],5:[function(require,module,exports){
-"use strict";var debug=require("debug");module.exports={frames:debug("rhea:frames"),raw:debug("rhea:raw"),reconnect:debug("rhea:reconnect"),events:debug("rhea:events"),message:debug("rhea:message"),flow:debug("rhea:flow"),io:debug("rhea:io")};
-},{"debug":15}],6:[function(require,module,exports){
-"use strict";function define_section(e,n,t){n.descriptor=e,unwrappers[e.symbolic]=n,unwrappers[Number(e.numeric).toString(10)]=n,t&&wrappers.push(t)}function define_composite_section(e){var n=types.define_composite(e);message[e.name]=n.create,by_descriptor[Number(n.descriptor.numeric).toString(10)]=n,by_descriptor[n.descriptor.symbolic]=n;var t=function(t,o){t[e.name]=new n(o.value)},o=function(t,o){o[e.name]&&(o[e.name].described?t.push(o[e.name].described()):t.push(n.create(o[e.name]).described()))};define_section(n.descriptor,t,o)}function define_map_section(e){var n={numeric:e.code};n.symbolic="amqp:"+e.name.replace(/_/g,"-")+":map";var t=function(n,t){n[e.name]=types.unwrap(t)},o=function(t,o){o[e.name]&&t.push(types.described(types.wrap_ulong(n.numeric),types.wrap_map(o[e.name])))};define_section(n,t,o)}function define_outcome(e){var n=types.define_composite(e);n.composite_type=e.name,message[e.name]=n.create,outcomes[Number(n.descriptor.numeric).toString(10)]=n,outcomes[n.des
 criptor.symbolic]=n,message["is_"+e.name]=function(n){if(n&&n.descriptor){var t=outcomes[n.descriptor.value];if(t)return t.descriptor.numeric==e.code}return!1}}var log=require("./log.js"),types=require("./types.js"),by_descriptor={},unwrappers={},wrappers=[],message={};define_composite_section({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}]}),define_map_section({name:"delivery_annotations",code:113}),define_map_section({name:"message_annotations",code:114}),define_composite_section({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"}]}),define_map_section({name:"application_properties",code:116}),define_section({numeric:119,symbolic:"amqp:value:*"},function(e,n){e.body=types.unwrap(n)},function(e,n){e.push(types.described(types.wrap_ulong(119),types.wrap(n.body)))}),define_map_section({name:"footer",code:120}),message.encode=function(e){var n=[];wrappers.forEach(function(t){t(n,e)});for(var t=new types.Writer,o=0;o<n.length;o++)log.message("Encoding section "+(o+1)+" of "+n.length+": "+n[o]),t.write(n[o]);var i=t.toBuffer();return log.message("encoded "+i.length+" bytes"),i},message.decode=function(e){for(var n={},t=new types.Reader(e);t.remaining();){var o=t.read();if(log.message("decoding section: "+JSON.stringify(o)+" of type: "+JSON.stringify(o.descriptor)),o.descriptor){var i=unwrappers[o.descriptor
 .value];i?i(n,o):console.log("WARNING: did not recognise message section with descriptor "+o.descriptor)}else console.log("WARNING: expected described message section got "+JSON.stringify(o))}return n};var outcomes={};message.unwrap_outcome=function(e){if(e&&e.descriptor){var n=outcomes[e.descriptor.value];if(n)return new n(e)}return console.log("unrecognised outcome"),e},message.are_outcomes_equivalent=function(e,n){return void 0===e&&void 0===n?!0:void 0===e||void 0===n?!1:e.descriptor.value==n.descriptor.value&&JSON.stringify(e)==JSON.stringify(n)},define_outcome({name:"received",code:35,fields:[{name:"section-number",type:"uint",mandatory:!0},{name:"section-offset",type:"ulong",mandatory:!0}]}),define_outcome({name:"accepted",code:36,fields:[]}),define_outcome({name:"rejected",code:37,fields:[{name:"error",type:"error"}]}),define_outcome({name:"released",code:38,fields:[]}),define_outcome({name:"modified",code:39,fields:[{name:"delivery-failed",type:"boolean"},{name:"undeliverab
 le-here",type:"boolean"},{name:"message-annotations",type:"fields"}]}),module.exports=message;
-},{"./log.js":5,"./types.js":12}],7:[function(require,module,exports){
-"use strict";function match(e,t){return t?Array.isArray(t)?t.indexOf(e)>-1:e===t:!1}var url=require("url"),simple_id_generator={counter:1,next:function(){return this.counter++}},Client=function(e,t){var i=url.parse(t);this.connection=e.connect({host:i.hostname,port:i.port}),this.connection.on("message",this._response.bind(this)),this.connection.on("receiver_open",this._ready.bind(this)),this.sender=this.connection.attach_sender(i.path.substr(1)),this.receiver=this.connection.attach_receiver({source:{dynamic:!0}}),this.id_generator=simple_id_generator,this.pending=[],this.outstanding={}};Client.prototype._request=function(e,t,i,n){var s={properties:{}};s.properties.subject=t,s.body=i,s.properties.message_id=e,s.properties.reply_to=this.receiver.remote.attach.source.address,this.outstanding[e]=n,this.sender.send(s)},Client.prototype._response=function(e){var t=e.message.properties.correlation_id,i=this.outstanding[t];i?"ok"===e.message.properties.subject?i(e.message.body):i(void 0,{na
 me:e.message.properties.subject,description:e.message.body}):console.log("no request pending for "+t+", ignoring response")},Client.prototype._ready=function(e){this._process_pending()},Client.prototype._process_pending=function(){for(var e=0;e<this.pending.length;e++){var t=this.pending[e];this._request(t.id,t.name,t.args,t.callback)}this.pending=[]},Client.prototype.call=function(e,t,i){var n=this.id_generator.next();this.receiver.is_open()&&0===this.pending.length?this._request(n,e,t,i):this.pending.push({name:e,args:t,callback:i,id:n})},Client.prototype.close=function(){this.receiver.close(),this.sender.close(),this.connection.close()},Client.prototype.define=function(e){this[e]=function(t,i){this.call(e,t,i)}};var Cache=function(e,t){this.ttl=e,this.purged=t,this.entries={},this.timeout=void 0};Cache.prototype.clear=function(){this.timeout&&clearTimeout(this.timeout),this.entries={}},Cache.prototype.put=function(e,t){this.entries[e]={value:t,last_accessed:Date.now()},this.timeo
 ut||(this.timeout=setTimeout(this.purge.bind(this),this.ttl))},Cache.prototype.get=function(e){var t=this.entries[e];return t?(t.last_accessed=Date.now(),t.value):void 0},Cache.prototype.purge=function(){var e=Date.now(),t=[],i=0;for(var n in this.entries)e-this.entries[n].last_accessed>=this.ttl?t.push(n):i++;for(var s=0;s<t.length;s++){var o=this.entries[t[s]];delete this.entries[t[s]],this.purged(o.value)}i&&!this.timeout&&(this.timeout=setTimeout(this.purge.bind(this),this.ttl))};var LinkCache=function(e,t){this.factory=e,this.cache=new Cache(t,function(e){e.close()})};LinkCache.prototype.clear=function(){this.cache.clear()},LinkCache.prototype.get=function(e){var t=this.cache.get(e);return void 0===t&&(t=this.factory(e),this.cache.put(e,t)),t};var Server=function(e,t,i){this.options=i||{};var n=url.parse(t);this.connection=e.connect({host:n.hostname,port:n.port}),this.connection.on("connection_open",this._connection_open.bind(this)),this.connection.on("message",this._request.bi
 nd(this)),this.receiver=this.connection.attach_receiver(n.path.substr(1)),this.callbacks={},this._send=void 0,this._clear=void 0};Server.prototype._connection_open=function(e){if(match("ANONYMOUS-RELAY",this.connection.remote.open.offered_capabilities)){var t=this.connection.attach_sender({target:{}});this._send=function(e){t.send(e)}}else{var i=new LinkCache(this.connection.attach_sender.bind(this.connection),this.options.cache_ttl||6e4);this._send=function(e){var t=i.get(e.properties.to);t&&t.send(e)},this._clear=function(){i.clear()}}},Server.prototype._respond=function(e){var t=this;return function(i,n){n?(e.properties.subject=n.name||"error",e.body=n.description||n):(e.properties.subject="ok",e.body=i),t._send(e)}},Server.prototype._request=function(e){var t=e.message,i={properties:{}};i.properties.to=t.properties.reply_to,i.properties.correlation_id=t.properties.message_id;var n=this.callbacks[t.properties.subject];n?n(t.body,this._respond(i)):(i.properties.subject="bad-method
 ",i.body="Unrecognised method "+t.properties.subject,this._send(i))},Server.prototype.bind_sync=function(e,t){this.callbacks[t||e.name]=function(t,i){var n=e(t);i(n)}},Server.prototype.bind=function(e,t){this.callbacks[t||e.name]=e},Server.prototype.close=function(){this._clear&&this._clear(),this.receiver.close(),this.connection.close()},module.exports={server:function(e,t,i){return new Server(e,t,i)},client:function(e,t){return new Client(e,t)}};
-},{"url":29}],8:[function(require,module,exports){
-(function (Buffer){
-"use strict";function intersection(e,t){return e.filter(function(e){return t.indexOf(e)>=0})}function extract(e){for(var t=[],s=0,n=0;n<e.length;)0===e[n]?(n>s?t.push(e.toString("utf8",s,n)):t.push(null),s=++n):++n;return n>s?t.push(e.toString("utf8",s,n)):t.push(null),t}var frames=require("./frames.js"),log=require("./log.js"),Transport=require("./transport.js"),sasl_codes={OK:0,AUTH:1,SYS:2,SYS_PERM:3,SYS_TEMP:4},SASL_PROTOCOL_ID=3,PlainServer=function(e){this.callback=e,this.outcome=void 0,this.username=void 0};PlainServer.prototype.start=function(e){var t=extract(e);3!==t.length&&this.connection.sasl_failed("Unexpected response in PLAIN, got "+t.length+" fields, expected 3"),this.callback(t[1],t[2])?(this.outcome=!0,this.username=t[1]):this.outcome=!1};var PlainClient=function(e,t){this.username=e,this.password=t};PlainClient.prototype.start=function(){var e=new Buffer(1+this.username.length+1+this.password.length);return e.writeUInt8(0,0),e.write(this.username,1),e.writeUInt8(0
 ,1+this.username.length),e.write(this.password,1+this.username.length+1),e};var AnonymousServer=function(){this.outcome=void 0,this.username=void 0};AnonymousServer.prototype.start=function(e){this.outcome=!0,this.username=e?e.toString("utf8"):"anonymous"};var AnonymousClient=function(e){this.username=e?e:"anonymous"};AnonymousClient.prototype.start=function(){var e=new Buffer(1+this.username.length);return e.writeUInt8(0,0),e.write(this.username,1),e};var ExternalServer=function(){this.outcome=void 0,this.username=void 0};ExternalServer.prototype.start=function(e){this.outcome=!0};var ExternalClient=function(){this.username=void 0};ExternalClient.prototype.start=function(){return null};var SaslServer=function(e,t){this.connection=e,this.transport=new Transport(e.amqp_transport.identifier,SASL_PROTOCOL_ID,frames.TYPE_SASL,this),this.next=e.amqp_transport,this.mechanisms=t,this.mechanism=void 0,this.outcome=void 0,this.username=void 0;var s=Object.getOwnPropertyNames(t);this.transpor
 t.encode(frames.sasl_frame(frames.sasl_mechanisms({sasl_server_mechanisms:s}).described()))};SaslServer.prototype.do_step=function(e){void 0===this.mechanism.outcome?this.transport.encode(frames.sasl_frame(frames.sasl_challenge({challenge:e}).described())):(this.outcome=this.mechanism.outcome?sasl_codes.OK:sasl_codes.AUTH,this.transport.encode(frames.sasl_frame(frames.sasl_outcome({code:this.outcome}).described())),this.outcome===sasl_codes.OK&&(this.username=this.mechanism.username,this.transport.write_complete=!0,this.transport.read_complete=!0))},SaslServer.prototype.on_sasl_init=function(e){var t=this.mechanisms[e.performative.mechanism];if(t){this.mechanism=t();var s=this.mechanism.start(e.performative.initial_response);this.do_step(s)}else this.outcome=sasl_codes.AUTH,this.transport.encode(frames.sasl_frame(frames.sasl_outcome({code:this.outcome}).described()))},SaslServer.prototype.on_sasl_response=function(e){this.do_step(this.mechanism.step(e.performative.response))},SaslSe
 rver.prototype.has_writes_pending=function(){return this.transport.has_writes_pending()||this.next.has_writes_pending()},SaslServer.prototype.write=function(e){return this.transport.write_complete&&0===this.transport.pending.length?this.next.write(e):this.transport.write(e)},SaslServer.prototype.read=function(e){return this.transport.read_complete?this.next.read(e):this.transport.read(e)};var SaslClient=function(e,t){this.connection=e,this.transport=new Transport(e.amqp_transport.identifier,SASL_PROTOCOL_ID,frames.TYPE_SASL,this),this.next=e.amqp_transport,this.mechanisms=t,this.mechanism=void 0,this.mechanism_name=void 0,this.failed=!1};SaslClient.prototype.on_sasl_mechanisms=function(e){for(var t=0;void 0===this.mechanism&&t<e.performative.sasl_server_mechanisms.length;t++){var s=e.performative.sasl_server_mechanisms[t],n=this.mechanisms[s];n&&(this.mechanism=n(),this.mechanism_name=s)}if(this.mechanism){var r=this.mechanism.start();this.transport.encode(frames.sasl_frame(frames.s
 asl_init({mechanism:this.mechanism_name,initial_response:r}).described()))}else this.failed=!0,this.connection.sasl_failed("No suitable mechanism; server supports "+e.performative.sasl_server_mechanisms)},SaslClient.prototype.on_sasl_challenge=function(e){var t=this.mechanism.step(e.performative.challenge);this.transport.encode(frames.sasl_frame(frames.sasl_response({response:t}).described()))},SaslClient.prototype.on_sasl_outcome=function(e){switch(e.performative.code){case sasl_codes.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: "+e.performative.code)}},SaslClient.prototype.has_writes_pending=function(){return this.transport.has_writes_pending()||this.next.has_writes_pending()},SaslClient.prototype.write=function(e){return this.transport.write_complete?this.next.write(e):this.transport.write(e)},SaslClient.prototype.read=function(e){return this.transport.read_c
 omplete?this.next.read(e):this.transport.read(e)};var default_server_mechanisms={enable_anonymous:function(){this.ANONYMOUS=function(){return new AnonymousServer}},enable_plain:function(e){this.PLAIN=function(){return new PlainServer(e)}}},default_client_mechanisms={enable_anonymous:function(e){this.ANONYMOUS=function(){return new AnonymousClient(e)}},enable_plain:function(e,t){this.PLAIN=function(){return new PlainClient(e,t)}},enable_external:function(){this.EXTERNAL=function(){return new ExternalClient}}};module.exports={Client:SaslClient,Server:SaslServer,server_mechanisms:function(){return Object.create(default_server_mechanisms)},client_mechanisms:function(){return Object.create(default_client_mechanisms)},server_add_external:function(e){return e.EXTERNAL=function(){return new ExternalServer},e}};
-}).call(this,require("buffer").Buffer)
-
-},{"./frames.js":3,"./log.js":5,"./transport.js":11,"buffer":19}],9:[function(require,module,exports){
-(function (Buffer){
-"use strict";function attach(e,t,i){var o=t?t:{};"string"==typeof t&&(o={},o[i]=t),o.name||(o.name=util.generate_uuid());var n=e(o.name,o);for(var s in{source:0,target:0})o[s]&&("string"==typeof o[s]&&(o[s]={address:o[s]}),n["set_"+s](o[s]));return n.attach(),n}var frames=require("./frames.js"),link=require("./link.js"),log=require("./log.js"),message=require("./message.js"),types=require("./types.js"),util=require("./util.js"),EndpointState=require("./endpoint.js"),EventEmitter=require("events").EventEmitter,CircularBuffer=function(e){this.capacity=e,this.size=0,this.head=0,this.tail=0,this.entries=[]};CircularBuffer.prototype.available=function(){return this.capacity-this.size},CircularBuffer.prototype.push=function(e){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]=e,this.tail=(this.tail+1)%this.capacity,this.size++},CircularBuffer.prototype.pop_if=funct
 ion(e){for(var t=0;this.size&&e(this.entries[this.head]);)this.entries[this.head]=void 0,this.head=(this.head+1)%this.capacity,this.size--,t++;return t},CircularBuffer.prototype.by_id=function(e){if(this.size>0){var t=e-this.entries[this.head].id;if(t<this.size)return this.entries[(this.head+t)%this.capacity]}},CircularBuffer.prototype.get_head=function(e){return this.size>0?this.entries[this.head]:void 0};var Outgoing=function(){this.deliveries=new CircularBuffer(2048),this.updated=[],this.next_delivery_id=0,this.next_pending_delivery=0,this.next_transfer_id=0,this.window=types.MAX_UINT,this.remote_next_transfer_id=void 0,this.remote_window=void 0};Outgoing.prototype.available=function(){return this.deliveries.available()},Outgoing.prototype.send=function(e,t,i,o){var n={id:this.next_delivery_id++,tag:t,link:e,data:i,format:o?o:0,sent:!1,settled:!1,state:void 0,remote_settled:!1,remote_state:void 0};return this.deliveries.push(n),n},Outgoing.prototype.on_begin=function(e){this.remo
 te_window=e.incoming_window},Outgoing.prototype.on_flow=function(e){this.remote_next_transfer_id=e.next_incoming_id,this.remote_window=e.incoming_window},Outgoing.prototype.on_disposition=function(e){for(var t=e.last?e.last:e.first,i=e.first;t>=i;i++){var o=this.deliveries.by_id(i);if(o||console.log("Could not find delivery for "+i+" ["+JSON.stringify(e)+"]"),o&&!o.remote_settled){var n=!1;e.settled&&(o.remote_settled=e.settled,n=!0),e.state&&e.state!==o.remote_state&&(o.remote_state=e.state,n=!0),n&&this.updated.push(o)}}},Outgoing.prototype.transfer_window=function(){return this.remote_window?this.remote_window-(this.next_transfer_id-this.remote_next_transfer_id):void 0},Outgoing.prototype.process=function(){for(;this.next_pending_delivery<this.next_delivery_id;){var e=this.deliveries.by_id(this.next_pending_delivery);if(!e){console.log("ERROR: Next pending delivery not found: "+this.next_pending_delivery);break}if(!e.link.has_credit()){log.flow("Link has no credit");break}if(e.li
 nk.delivery_count++,e.transfers_required=1,!(this.transfer_window()>=e.transfers_required)){log.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+=e.transfers_required,this.window-=e.transfers_required,e.link.session.output(frames.transfer({handle:e.link.local.handle,message_format:e.format,delivery_id:e.id,delivery_tag:e.tag}).described(),e.data),e.link.credit--,this.next_pending_delivery++}for(var t=0;t<this.updated.length;t++){var e=this.updated[t];e.remote_state&&(e.remote_state=message.unwrap_outcome(e.remote_state),e.remote_state&&e.remote_state.constructor.composite_type&&e.link.dispatch(e.remote_state.constructor.composite_type,e.link._context({delivery:e}))),e.remote_settled&&e.link.dispatch("settled",e.link._context({delivery:e}))}this.updated=[],this.deliveries.pop_if(function(e){return e.se
 ttled&&e.remote_settled})};var Incoming=function(){this.deliveries=new CircularBuffer(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};Incoming.prototype.update=function(e,t,i){e&&(e.settled=t,void 0!==i&&(e.state=i),e.remote_settled||this.updated.push(e),e.link.connection._register())},Incoming.prototype.on_transfer=function(e,t){if(this.next_transfer_id++,t.is_open()){void 0===this.next_delivery_id&&(this.next_delivery_id=e.performative.delivery_id);var i,o,n=this.deliveries.get_head();if(n&&n.incomplete){if(void 0!==e.performative.delivery_id&&this.next_delivery_id!=e.performative.delivery_id)throw Error("frame sequence error: delivery "+this.next_delivery_id+" not complete, got "+e.performative.delivery_id);i=n,o=Buffer.concat([i.data,e.payload],i.data.size()+e.payload.size())}else{if(this.next_delivery_id!==e.performative.delivery_id)throw Error("frame sequence error: expec
 ted "+this.next_delivery_id+", got "+e.performative.delivery_id);i={id:e.performative.delivery_id,tag:e.performative.delivery_tag,link:t,settled:!1,state:void 0,remote_settled:e.performative.settled,remote_state:void 0};var s=this;i.update=function(e,t){s.update(i,e,t)},this.deliveries.push(i),o=e.payload}i.incomplete=e.performative.more,i.incomplete?i.data=o:(t.credit--,t.delivery_count++,this.next_delivery_id++,t.dispatch("message",t._context({message:message.decode(o),delivery:i})))}},Incoming.prototype.process=function(){if(this.updated.length>0){for(var e,t,i,o=0;o<this.updated.length;o++){var n=this.updated[o];void 0===e&&(e=n,t=n,i=n.id),message.are_outcomes_equivalent(t.state,n.state)&&t.settled===n.settled&&i===n.id?(t.id!==n.id&&(t=n),i++):(e.link.session.output(frames.disposition({role:!0,first:e.id,last:t.id,state:e.state,settled:e.settled}).described()),e=n,t=n,i=n.id)}void 0!==e&&void 0!==t&&e.link.session.output(frames.disposition({role:!0,first:e.id,last:t.id,state:e
 .state,settled:e.settled}).described()),this.updated=[]}this.deliveries.pop_if(function(e){return e.settled})},Incoming.prototype.on_begin=function(e){this.remote_window=e.outgoing_window},Incoming.prototype.on_flow=function(e){this.remote_next_transfer_id=e.next_outgoing_id,this.remote_window=e.outgoing_window},Incoming.prototype.on_disposition=function(e){for(var t=e.last?e.last:e.first,i=e.first;t>=i;i++){var o=this.deliveries.by_id(i);if(o||console.log("Could not find delivery for "+i),o&&!o.remote_settled){var n=!1;e.settled&&(o.remote_settled=e.settled,n=!0),e.state&&e.state!==o.remote_state&&(o.remote_state=e.state,n=!0),n&&(console.log(o.link.connection.options.id+" added delivery to updated list following receipt of disposition for incoming deliveries"),this.updated.push(o))}}};var Session=function(e,t){this.connection=e,this.outgoing=new Outgoing,this.incoming=new Incoming,this.state=new EndpointState,this.local={channel:t,handles:{}},this.local.begin=frames.begin({next_ou
 tgoing_id:this.outgoing.next_transfer_id,incoming_window:this.incoming.window,outgoing_window:this.outgoing.window}),this.local.end=frames.end(),this.remote={handles:{}},this.links={},this.options={}};Session.prototype=Object.create(EventEmitter.prototype),Session.prototype.constructor=Session,Session.prototype.reset=function(){this.state.disconnected(),this.outgoing=new Outgoing,this.incoming=new Incoming,this.remote={handles:{}};for(var e in this.links)this.links[e].reset()},Session.prototype.dispatch=function(e,t){log.events("Session got event: "+e),this.listeners(e).length?EventEmitter.prototype.emit.apply(this,arguments):this.connection.dispatch.apply(this.connection,arguments)},Session.prototype.output=function(e,t){this.connection._write_frame(this.local.channel,e,t)},Session.prototype.create_sender=function(e,t){return this.create_link(e,link.Sender,t)},Session.prototype.create_receiver=function(e,t){return this.create_link(e,link.Receiver,t)},Session.prototype.get_option=fu
 nction(e,t){return void 0!==this.options[e]?this.options[e]:this.connection.get_option(e,t)},Session.prototype.attach_sender=function(e){return attach(this.create_sender.bind(this),e,"target")},Session.prototype.open_sender=Session.prototype.attach_sender,Session.prototype.attach_receiver=function(e){return attach(this.create_receiver.bind(this),e,"source")},Session.prototype.open_receiver=Session.prototype.attach_receiver,Session.prototype.create_link=function(e,t,i){for(var o=0;this.local.handles[o];)o++;var n=new t(this,e,o,i);return this.links[e]=n,this.local.handles[o]=n,n},Session.prototype.begin=function(){this.state.open()&&this.connection._register()},Session.prototype.open=Session.prototype.begin,Session.prototype.end=function(){this.state.close()&&this.connection._register()},Session.prototype.close=Session.prototype.end,Session.prototype.is_open=function(){return this.connection.is_open()&&this.state.is_open()},Session.prototype.is_closed=function(){return this.connectio
 n.is_closed()||this.state.is_closed()},Session.prototype._process=function(){do{this.state.need_open()&&this.output(this.local.begin.described()),this.outgoing.process(),this.incoming.process();for(var e in this.links)this.links[e]._process();this.state.need_close()&&this.output(this.local.end.described())}while(!this.state.has_settled())},Session.prototype.send=function(e,t,i,o){var n=this.outgoing.send(e,t,i,o);return this.connection._register(),n},Session.prototype._write_flow=function(e){var t={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};e&&(t.delivery_count=e.delivery_count,t.handle=e.local.handle,t.link_credit=e.credit),this.output(frames.flow(t).described())},Session.prototype.on_begin=function(e){if(!this.state.remote_opened())throw Error("Begin already received");this.remote.channel||(this.remote.channel=e.channel),this.remote.begin=e.performative,t
 his.outgoing.on_begin(e.performative),this.incoming.on_begin(e.performative),this.open(),this.dispatch("session_open",this._context())},Session.prototype.on_end=function(e){if(!this.state.remote_closed())throw Error("End already received");this.remote.end=e.performative,this.close(),this.dispatch("session_close",this._context())},Session.prototype.on_attach=function(e){var t=e.performative.name,i=this.links[t];i||(i=e.performative.role?this.create_sender(t):this.create_receiver(t)),this.remote.handles[e.performative.handle]=i,i.on_attach(e),i.remote.attach=e.performative},Session.prototype.on_disposition=function(e){e.performative.role?(log.events("Received disposition for outgoing transfers"),this.outgoing.on_disposition(e.performative)):(log.events("Received disposition for incoming transfers"),this.incoming.on_disposition(e.performative)),this.connection._register()},Session.prototype.on_flow=function(e){this.outgoing.on_flow(e.performative),this.incoming.on_flow(e.performative),
 void 0!==e.performative.handle&&this._get_link(e).on_flow(e),this.connection._register()},Session.prototype._context=function(e){var t=e?e:{};return t.session=this,this.connection._context(t)},Session.prototype._get_link=function(e){var t=e.performative.handle,i=this.remote.handles[t];if(!i)throw Error("Invalid handle "+t);return i},Session.prototype.on_detach=function(e){this._get_link(e).on_detach(e);var t=e.performative.handle,i=this.remote.handles[t];delete this.remote.handles[t],delete this.local.handles[i.local.handle],delete this.links[i.name]},Session.prototype.on_transfer=function(e){this.incoming.on_transfer(e,this._get_link(e))},module.exports=Session;
-}).call(this,require("buffer").Buffer)
-
-},{"./endpoint.js":2,"./frames.js":3,"./link.js":4,"./log.js":5,"./message.js":6,"./types.js":12,"./util.js":13,"buffer":19,"events":23}],10:[function(require,module,exports){
-"use strict";function define_terminus(e){var t=types.define_composite(e);terminus[e.name]=t.create,by_descriptor[Number(t.descriptor.numeric).toString(10)]=t,by_descriptor[t.descriptor.symbolic]=t}var types=require("./types.js"),terminus={},by_descriptor={};terminus.unwrap=function(e){if(e&&e.descriptor){var t=by_descriptor[e.descriptor.value];if(t)return new t(e.value);console.log("Unknown terminus: "+e.descriptor)}return null},define_terminus({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}]}),define_terminus({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}]}),module.exports=terminus;
-},{"./types.js":12}],11:[function(require,module,exports){
-(function (Buffer){
-"use strict";var frames=require("./frames.js"),log=require("./log.js"),Transport=function(e,i,r,t){this.identifier=e,this.protocol_id=i,this.frame_type=r,this.handler=t,this.pending=[],this.header_sent=void 0,this.header_received=void 0,this.write_complete=!1,this.read_complete=!1};Transport.prototype.has_writes_pending=function(){return this.pending.length>0},Transport.prototype.encode=function(e){var i=frames.write_frame(e);log.frames("["+this.identifier+"] PENDING: "+JSON.stringify(e)),this.pending.push(i)},Transport.prototype.write=function(e){if(!this.header_sent){var i=new Buffer(8),r={protocol_id:this.protocol_id,major:1,minor:0,revision:0};frames.write_header(i,r),e.write(i),this.header_sent=r}for(var t=0;t<this.pending.length;t++)e.write(this.pending[t]),log.raw("["+this.identifier+"] SENT: "+JSON.stringify(this.pending[t]));this.pending=[]},Transport.prototype.read=function(e){var i=0;if(!this.header_received){if(e.length<8)return i;if(this.header_received=frames.read_head
 er(e),log.frames("["+this.identifier+"] RECV: "+JSON.stringify(this.header_received)),this.header_received.protocol_id!==this.protocol_id)throw Error("Invalid AMQP protocol id "+this.header_received.protocol_id+" expecting: "+this.protocol_id);i=8}for(;i<e.length&&!this.read_complete;){var r=e.readUInt32BE(i);if(log.io("["+this.identifier+"] got frame of size "+r),e.length<i+r){log.io("["+this.identifier+"] incomplete frame; have only "+(e.length-i)+" of "+r);break}var t=frames.read_frame(e.slice(i,i+r));if(log.frames("["+this.identifier+"] RECV: "+JSON.stringify(t)),t.type!==this.frame_type)throw Error("Invalid frame type: "+t.type);i+=r,t.performative&&t.performative.dispatch(this.handler,t)}return i},module.exports=Transport;
-}).call(this,require("buffer").Buffer)
-
-},{"./frames.js":3,"./log.js":5,"buffer":19}],12:[function(require,module,exports){
-(function (Buffer){
-"use strict";function Typed(t,e){this.type=t,this.value=e}function hex(t){return Number(t).toString(16)}function define_type(t,e,r,i){var n,o=e>>>4;switch(n=4===o?function(){this.type=n,this.value=i}:14===o||15===o?function(t,e,r){this.type=n,this.value=t,this.array_constructor={typecode:e},r&&(this.array_constructor.descriptor=r)}:function(t){this.type=n,this.value=t},n.typecode=e,n.prototype=Object.create(Typed.prototype),n.toString=function(){return t+"#"+hex(e)},o){case 4:n.width=0,n.category="fixed";break;case 5:n.width=1,n.category="fixed";break;case 6:n.width=2,n.category="fixed";break;case 7:n.width=4,n.category="fixed";break;case 8:n.width=8,n.category="fixed";break;case 9:n.width=16,n.category="fixed";break;case 10:n.width=1,n.category="variable";break;case 11:n.width=4,n.category="variable";break;case 12:n.width=1,n.category="compound";break;case 13:n.width=4,n.category="compound";break;case 14:n.width=1,n.category="array";break;case 15:n.width=4,n.category="array"}if(r)f
 or(var s in r)n[s]=r[s];return types.by_code[n.typecode]=n,types[t]=n,n}function buffer_ops(t){return{read:function(e,r){return e["read"+t](r)},write:function(e,r,i){e["write"+t](r,i)}}}function write_ulong(t,e,r){if("number"==typeof e||e instanceof Number){var i=e/MAX_UINT,n=e%MAX_UINT;t.writeUInt32BE(i,r),t.writeUInt32BE(n,r+4)}else e.copy(t,r)}function read_ulong(t,e){var r=t.readUInt32BE(e),i=t.readUInt32BE(e+4);return 2097153>r?r*MAX_UINT+i:t.slice(e,e+8)}function write_long(t,e,r){if("number"==typeof e||e instanceof Number){var i=Math.abs(e),n=i/MAX_UINT,o=i%MAX_UINT;if(t.writeInt32BE(n,r),t.writeUInt32BE(o,r+4),0>e)for(var s=1,p=0;8>p;p++){var a=r+(7-p),e=(255^t[a])+s;t[a]=255&e,s=e>>8}}else e.copy(t,r)}function read_long(t,e){var r=t.readInt32BE(e),i=t.readUInt32BE(e+4);return 2097153>r&&r>-2097153?r*MAX_UINT+i:t.slice(e,e+8)}function is_one_of(t,e){for(var r=0;r<e.length;r++)if(t.type.typecode===e[r].typecode)return!0;return!1}function mapify(t){for(var e={},r=0;r+1<t.lengt
 h;)e[t[r++]]=t[r++];return e}function get_type(t){var e=types.by_code[t];if(!e)throw Error("Unrecognised typecode: "+hex(t));return e}function max(t,e){return t>e?t:e}function get_constructor(t){if("symbol"===t)return{typecode:types.Sym8.typecode};throw Error("TODO: Array of type "+t+" not yet supported")}function wrap_field(t,e){if(void 0!==e&&null!==e){if(Array.isArray(e)){if(!t.multiple)throw Error("Field "+t.name+" does not support multiple values, got "+JSON.stringify(e));var r=get_constructor(t.type);return types.wrap_array(e,r.typecode,r.descriptor)}if("*"===t.type)return e;var i=types["wrap_"+t.type];if(i)return i(e);throw Error("No wrapper for field "+t.name+" of type "+t.type)}if(t.mandatory)throw Error("Field "+t.name+" is mandatory");return new types.Null}function get_accessors(t,e){var r=function(){return"*"===e.type?this.value[t]:types.unwrap(this.value[t])},i=function(r){this.value[t]=wrap_field(e,r)};return{get:r,set:i,enumerable:!0,configurable:!1}}function add_type
 (t){var e=types.define_composite(t);types["wrap_"+t.name]=function(t){return e.create(t).described()},by_descriptor[Number(e.descriptor.numeric).toString(10)]=e,by_descriptor[e.descriptor.symbolic]=e}Typed.prototype.toString=function(){return this.value?this.value.toString():null},Typed.prototype.toLocaleString=function(){return this.value?this.value.toLocaleString():null},Typed.prototype.valueOf=function(){return this.value},Typed.prototype.toJSON=function(){return this.value&&this.value.toJSON?this.value.toJSON():this.value};var types={by_code:{}};Object.defineProperty(types,"MAX_UINT",{value:4294967295,writable:!1,configurable:!1}),Object.defineProperty(types,"MAX_USHORT",{value:65535,writable:!1,configurable:!1});var MAX_UINT=4294967296;define_type("Null",64,void 0,null),define_type("Boolean",86,buffer_ops("UInt8")),define_type("True",65,void 0,!0),define_type("False",66,void 0,!1),define_type("Ubyte",80,buffer_ops("UInt8")),define_type("Ushort",96,buffer_ops("UInt16BE")),define
 _type("Uint",112,buffer_ops("UInt32BE")),define_type("SmallUint",82,buffer_ops("UInt8")),define_type("Uint0",67,void 0,0),define_type("Ulong",128,{write:write_ulong,read:read_ulong}),define_type("SmallUlong",83,buffer_ops("UInt8")),define_type("Ulong0",68,void 0,0),define_type("Byte",81,buffer_ops("Int8")),define_type("Short",97,buffer_ops("Int16BE")),define_type("Int",113,buffer_ops("Int32BE")),define_type("SmallInt",84,buffer_ops("Int8")),define_type("Long",129,{write:write_long,read:read_long}),define_type("SmallLong",85,buffer_ops("Int8")),define_type("Float",114,buffer_ops("Float")),define_type("Double",130,buffer_ops("Double")),define_type("Decimal32",116),define_type("Decimal64",132),define_type("Decimal128",148),define_type("CharUTF32",115),define_type("Timestamp",131),define_type("Uuid",152),define_type("Vbin8",160),define_type("Vbin32",176),define_type("Str8",161,{encoding:"utf8"}),define_type("Str32",177,{encoding:"utf8"}),define_type("Sym8",163,{encoding:"ascii"}),define
 _type("Sym32",179,{encoding:"ascii"}),define_type("List0",69,void 0,[]),define_type("List8",192),define_type("List32",208),define_type("Map8",193),define_type("Map32",209),define_type("Array8",224),define_type("Array32",240),types.is_ulong=function(t){return is_one_of(t,[types.Ulong,types.Ulong0,types.SmallUlong])},types.is_string=function(t){return is_one_of(t,[types.Str8,types.Str32])},types.is_symbol=function(t){return is_one_of(t,[types.Sym8,types.Sym32])},types.is_list=function(t){return is_one_of(t,[types.List0,types.List8,types.List32])},types.is_map=function(t){return is_one_of(t,[types.Map8,types.Map32])},types.wrap_boolean=function(t){return new types.Boolean(t)},types.wrap_ulong=function(t){return 0===t?new types.Ulong0:t>255?new types.Ulong(t):new types.SmallUlong(t)},types.wrap_uint=function(t){return 0===t?new types.Uint0:t>255?new types.Uint(t):new types.SmallUint(t)},types.wrap_ushort=function(t){return new types.Ushort(t)},types.wrap_ubyte=function(t){return new typ
 es.Ubyte(t)},types.wrap_long=function(t){return t>127||-128>t?new types.Long(t):new types.SmallLong(t)},types.wrap_int=function(t){return t>127||-128>t?new types.Int(t):new types.SmallInt(t)},types.wrap_short=function(t){return new types.Short(t)},types.wrap_byte=function(t){return new types.Byte(t)},types.wrap_float=function(t){return new types.Float(t)},types.wrap_double=function(t){return new types.Double(t)},types.wrap_timestamp=function(t){return new types.Timestamp(t)},types.wrap_binary=function(t){return t.length>255?new types.Vbin32(t):new types.Vbin8(t)},types.wrap_string=function(t){return t.length>255?new types.Str32(t):new types.Str8(t)},types.wrap_symbol=function(t){return t.length>255?new types.Sym32(t):new types.Sym8(t)},types.wrap_list=function(t){if(0===t.length)return new types.List0;var e=t.map(types.wrap);return new types.List32(e)},types.wrap_map=function(t,e){var r=[];for(var i in t)r.push(e?e(i):types.wrap(i)),r.push(types.wrap(t[i]));return new types.Map32(r)
 },types.wrap_symbolic_map=function(t){return types.wrap_map(t,types.wrap_symbol)},types.wrap_array=function(t,e,r){if(e)return new types.Array32(t,e,r);throw Error("An array must specify a type for its elements")},types.wrap=function(t){var e=typeof t;if("string"===e)return types.wrap_string(t);if("boolean"==e)return t?new types.True:new types.False;if("number"==e||t instanceof Number){if(isNaN(t))throw Error("Cannot wrap NaN! "+t);return Math.floor(t)-t!==0?new types.Double(t):t>0?types.wrap_uint(t):types.wrap_int(t)}return t instanceof Date?void 0:t instanceof Typed?t:"undefined"==e||null===t?new types.Null:Array.isArray(t)?types.wrap_list(t):types.wrap_map(t)},types.wrap_described=function(t,e){var r=types.wrap(t);return e&&("string"==typeof e?r=types.described(types.wrap_string(e),r):("number"==typeof e||e instanceof Number)&&(r=types.described(types.wrap_ulong(e),r))),r},types.wrap_message_id=function(t){var e=typeof t;if("string"===e)return types.wrap_string(t);if("number"==e|
 |t instanceof Number)return types.wrap_ulong(t);throw Error("invalid message id:"+t)},types.wrap_delivery_state=function(t){return new Null};var by_descriptor={};types.unwrap=function(t,e){if(t instanceof Typed){if(t.descriptor){var r=by_descriptor[t.descriptor.value];if(r)return new r(t.value);if(e)return t}var i=types.unwrap(t.value,!0);return types.is_map(t)?mapify(i):i}return Array.isArray(t)?t.map(function(t){return types.unwrap(t,!0)}):t},types.described=function(t,e){var r=Object.create(e);return t.length?(r.descriptor=t.shift(),described(t,r)):(r.descriptor=t,r)},types.Reader=function(t){this.buffer=t,this.position=0},types.Reader.prototype.read_typecode=function(){return this.read_uint(1)},types.Reader.prototype.read_uint=function(t){var e=this.position;this.position+=t;var r=t>1?"readUInt"+8*t+"BE":"readUInt8";return this.buffer[r](e)},types.Reader.prototype.read_fixed_width=function(t){var e=this.position;return this.position+=t.width,t.read?t.read(this.buffer,e):this.buf
 fer.slice(e,this.position)},types.Reader.prototype.read_variable_width=function(t){var e=this.read_uint(t.width),r=this.read_bytes(e);return t.encoding?r.toString(t.encoding):r},types.Reader.prototype.read=function(){var t=this.read_constructor(),e=this.read_value(get_type(t.typecode));return t.descriptor?types.described(t.descriptor,e):e},types.Reader.prototype.read_constructor=function(){var t=this.read_typecode();if(0===t){var e=[];e.push(this.read());for(var r=this.read_constructor();r.descriptor;)e.push(r.descriptor),r=this.read_constructor();return{typecode:r.typecode,descriptor:1==e.length?e[0]:e}}return{typecode:t}},types.Reader.prototype.read_value=function(t){if(0===t.width)return new t;if("fixed"===t.category)return new t(this.read_fixed_width(t));if("variable"===t.category)return new t(this.read_variable_width(t));if("compound"===t.category)return this.read_compound(t);if("array"===t.category)return this.read_array(t);throw Error("Invalid category for type: "+t)},types.R
 eader.prototype.read_multiple=function(t,e){for(var r=e?e:this.read.bind(this),i=[];i.length<t;)i.push(r.apply(this));return i},types.Reader.prototype.read_size_count=function(t){return{size:this.read_uint(t),count:this.read_uint(t)}},types.Reader.prototype.read_compound=function(t){var e=this.read_size_count(t.width);return new t(this.read_multiple(e.count))},types.Reader.prototype.read_array=function(t){var e=this.read_size_count(t.width),r=this.read_constructor(),i=new t(this.read_multiple(e.count,this.read_value.bind(this,get_type(r.typecode))),r.typecode,r.descriptor);return i},types.Reader.prototype.toString=function(){var t="buffer@"+this.position;this.position&&(t+=": ");for(var e=this.position;e<this.buffer.length;e++)e>0&&(t+=","),t+="0x"+Number(this.buffer[e]).toString(16);return t},types.Reader.prototype.reset=function(){this.position=0},types.Reader.prototype.skip=function(t){this.position+=t},types.Reader.prototype.read_bytes=function(t){var e=this.position;return this
 .position+=t,this.buffer.slice(e,this.position)},types.Reader.prototype.remaining=function(){return this.buffer.length-this.position},types.Writer=function(t){this.buffer=t?t:new Buffer(1024),this.position=0},types.Writer.prototype.toBuffer=function(){return this.buffer.slice(0,this.position)},types.Writer.prototype.ensure=function(t){if(this.buffer.length<t){var e=new Buffer(max(2*this.buffer.length,t));this.buffer.copy(e),this.buffer=e}},types.Writer.prototype.write_typecode=function(t){this.write_uint(t,1)},types.Writer.prototype.write_uint=function(t,e){var r=this.position;this.ensure(this.position+e),this.position+=e;var i=e>1?"writeUInt"+8*e+"BE":"writeUInt8";if(!this.buffer[i])throw Error("Buffer doesn't define "+i);return this.buffer[i](t,r)},types.Writer.prototype.write_fixed_width=function(t,e){var r=this.position;if(this.ensure(this.position+t.width),this.position+=t.width,t.write)t.write(this.buffer,e,r);else{if(!e.copy)throw Error("Can't handle write for "+t);e.copy(thi
 s.buffer,r)}},types.Writer.prototype.write_variable_width=function(t,e){var r=t.encoding?new Buffer(e,t.encoding):new Buffer(e);this.write_uint(r.length,t.width),this.write_bytes(r)},types.Writer.prototype.write_bytes=function(t){var e=this.position;this.ensure(this.position+t.length),this.position+=t.length,t.copy(this.buffer,e)},types.Writer.prototype.write_constructor=function(t,e){e&&(this.write_typecode(0),this.write(e)),this.write_typecode(t)},types.Writer.prototype.write=function(t){if(!(t instanceof Typed))throw Error("Can't write "+JSON.stringify(t));this.write_constructor(t.type.typecode,t.descriptor),this.write_value(t.type,t.value,t.array_constructor)},types.Writer.prototype.write_value=function(t,e,r){if(0!==t.width)if("fixed"===t.category)this.write_fixed_width(t,e);else if("variable"===t.category)this.write_variable_width(t,e);else if("compound"===t.category)this.write_compound(t,e);else{if("array"!==t.category)throw Error("Invalid category "+t.category+" for type: "+
 t);this.write_array(t,e,r)}},types.Writer.prototype.backfill_size=function(t,e){var r=this.position-e;this.position=e,this.write_uint(r-t,t),this.position+=r-t},types.Writer.prototype.write_compound=function(t,e){var r=this.position;this.position+=t.width,this.write_uint(e.length,t.width);for(var i=0;i<e.length;i++)void 0===e[i]||null===e[i]?this.write(new types.Null):this.write(e[i]);this.backfill_size(t.width,r)},types.Writer.prototype.write_array=function(t,e,r){var i=this.position;this.position+=t.width,this.write_uint(e.length,t.width),this.write_constructor(r.typecode,r.descriptor);for(var t=get_type(r.typecode),n=0;n<e.length;n++)this.write_value(t,e[n]);this.backfill_size(t.width,i)},types.Writer.prototype.toString=function(){var t="buffer@"+this.position;this.position&&(t+=": ");for(var e=0;e<this.position;e++)e>0&&(t+=","),t+=("00"+Number(this.buffer[e]).toString(16)).slice(-2);return t},types.Writer.prototype.skip=function(t){this.ensure(this.position+t),this.position+=t}
 ,types.Writer.prototype.clear=function(){this.buffer.fill(0),this.position=0},types.Writer.prototype.remaining=function(){return this.buffer.length-this.position},types.define_composite=function(t){var e=function(t){this.value=t?t:[]};e.descriptor={numeric:t.code,symbolic:"amqp:"+t.name+":list"},e.prototype.dispatch=function(e,r){e["on_"+t.name](r)};for(var r=0;r<t.fields.length;r++){var i=t.fields[r];Object.defineProperty(e.prototype,i.name,get_accessors(r,i))}return e.toString=function(){return t.name+"#"+Number(t.code).toString(16)},e.prototype.toJSON=function(){var t={};for(var e in this)"value"!==e&&this[e]&&(t[e]=this[e]);return t},e.create=function(t){var r=new e;for(var i in t)r[i]=t[i];return r},e.prototype.described=function(t){return types.described(types.wrap_ulong(e.descriptor.numeric),types.wrap_list(this.value))},e},add_type({name:"error",code:29,fields:[{name:"condition",type:"symbol",mandatory:!0},{name:"description",type:"string"},{name:"info",type:"map"}]}),module
 .exports=types;
-}).call(this,require("buffer").Buffer)
-
-},{"buffer":19}],13:[function(require,module,exports){
-"use strict";var util={};util.generate_uuid=function(){var x="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(x){var t=16*Math.random()|0,r="x"==x?t:3&t|8;return r.toString(16)});return x},util.clone=function(x){for(var t=Object.create(x.prototype||{}),r=Object.getOwnPropertyNames(x),e=0;e<r.length;e++){var n=r[e];t[n]=x[n]}return t},module.exports=util;
-},{}],14:[function(require,module,exports){
-(function (Buffer){
-"use strict";function nulltransform(n){return n}function from_arraybuffer(n){return n instanceof ArrayBuffer?new Buffer(new Uint8Array(n)):new Buffer(n)}function to_typedarray(n){return new Uint8Array(n)}function wrap(n){var r=nulltransform,e=nulltransform;return n.binaryType&&(n.binaryType="arraybuffer",r=from_arraybuffer,e=to_typedarray),{end:function(){n.close()},write:function(r){n.send(e(r),{binary:!0})},on:function(e,t){"data"===e?n.onmessage=function(n){t(r(n.data))}:"end"===e?n.onclose=t:"error"===e?n.onerror=t:console.log("ERROR: Attempt to set unrecognised handler on websocket wrapper: "+e)},get_id_string:function(){return n.url}}}module.exports={connect:function(n){return function(r,e){return function(){return{connect:function(t,o,u,a){var f=new n(r,e);return f.onopen=a,wrap(f)}}}}},wrap:wrap};
-}).call(this,require("buffer").Buffer)
-
-},{"buffer":19}],15:[function(require,module,exports){
-function useColors(){return"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function formatArgs(){var o=arguments,e=this.useColors;if(o[0]=(e?"%c":"")+this.namespace+(e?" %c":" ")+o[0]+(e?"%c ":" ")+"+"+exports.humanize(this.diff),!e)return o;var r="color: "+this.color;o=[o[0],r,"color: inherit"].concat(Array.prototype.slice.call(o,1));var t=0,s=0;return o[0].replace(/%[a-z%]/g,function(o){"%%"!==o&&(t++,"%c"===o&&(s=t))}),o.splice(s,0,r),o}function log(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(o){try{null==o?exports.storage.removeItem("debug"):exports.storage.debug=o}catch(e){}}function load(){var o;try{o=exports.storage.debug}catch(e){}return o}function localstorage(){try{return window.localStorage}catch(o){}}exports=module.exports=require("./deb
 ug"),exports.log=log,exports.formatArgs=formatArgs,exports.save=save,exports.load=load,exports.useColors=useColors,exports.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:localstorage(),exports.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],exports.formatters.j=function(o){return JSON.stringify(o)},exports.enable(load());
-},{"./debug":16}],16:[function(require,module,exports){
-function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(e){function r(){}function o(){var e=o,r=+new Date,s=r-(prevTime||r);e.diff=s,e.prev=prevTime,e.curr=r,prevTime=r,null==e.useColors&&(e.useColors=exports.useColors()),null==e.color&&e.useColors&&(e.color=selectColor());var t=Array.prototype.slice.call(arguments);t[0]=exports.coerce(t[0]),"string"!=typeof t[0]&&(t=["%o"].concat(t));var n=0;t[0]=t[0].replace(/%([a-z%])/g,function(r,o){if("%%"===r)return r;n++;var s=exports.formatters[o];if("function"==typeof s){var p=t[n];r=s.call(e,p),t.splice(n,1),n--}return r}),"function"==typeof exports.formatArgs&&(t=exports.formatArgs.apply(e,t));var p=o.log||exports.log||console.log.bind(console);p.apply(e,t)}r.enabled=!1,o.enabled=!0;var s=exports.enabled(e)?o:r;return s.namespace=e,s}function enable(e){exports.save(e);for(var r=(e||"").split(/[\s,]+/),o=r.length,s=0;o>s;s++)r[s]&&(e=r[s].replace(/\*/g,".*?"),"-"===e[0]?exports.skips.push(new RegExp("
 ^"+e.substr(1)+"$")):exports.names.push(new RegExp("^"+e+"$")))}function disable(){exports.enable("")}function enabled(e){var r,o;for(r=0,o=exports.skips.length;o>r;r++)if(exports.skips[r].test(e))return!1;for(r=0,o=exports.names.length;o>r;r++)if(exports.names[r].test(e))return!0;return!1}function coerce(e){return e instanceof Error?e.stack||e.message:e}exports=module.exports=debug,exports.coerce=coerce,exports.disable=disable,exports.enable=enable,exports.enabled=enabled,exports.humanize=require("ms"),exports.names=[],exports.skips=[],exports.formatters={};var prevColor=0,prevTime;
-},{"ms":17}],17:[function(require,module,exports){
-function parse(e){if(e=""+e,!(e.length>1e4)){var a=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(a){var r=parseFloat(a[1]),c=(a[2]||"ms").toLowerCase();switch(c){case"years":case"year":case"yrs":case"yr":case"y":return r*y;case"days":case"day":case"d":return r*d;case"hours":case"hour":case"hrs":case"hr":case"h":return r*h;case"minutes":case"minute":case"mins":case"min":case"m":return r*m;case"seconds":case"second":case"secs":case"sec":case"s":return r*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r}}}}function short(e){return e>=d?Math.round(e/d)+"d":e>=h?Math.round(e/h)+"h":e>=m?Math.round(e/m)+"m":e>=s?Math.round(e/s)+"s":e+"ms"}function long(e){return plural(e,d,"day")||plural(e,h,"hour")||plural(e,m,"minute")||plural(e,s,"second")||e+" ms"}function plural(s,e,a){return e>s?void 0:1.5*e>s?Math.floor(s/e)+" "+a:Math.ceil(s/e)+" "+a+"s"}var s=1e3,m=60*s,h=60*m,d=24*h
 ,y=365.25*d;module.exports=function(s,e){return e=e||{},"string"==typeof s?parse(s):e["long"]?long(s):short(s)};
-},{}],18:[function(require,module,exports){
-
-},{}],19:[function(require,module,exports){
-(function (global){
-"use strict";function typedArraySupport(){try{var t=new Uint8Array(1);return t.foo=function(){return 42},42===t.foo()&&"function"==typeof t.subarray&&0===t.subarray(1,1).byteLength}catch(e){return!1}}function kMaxLength(){return Buffer.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function Buffer(t){return this instanceof Buffer?(Buffer.TYPED_ARRAY_SUPPORT||(this.length=0,this.parent=void 0),"number"==typeof t?fromNumber(this,t):"string"==typeof t?fromString(this,t,arguments.length>1?arguments[1]:"utf8"):fromObject(this,t)):arguments.length>1?new Buffer(t,arguments[1]):new Buffer(t)}function fromNumber(t,e){if(t=allocate(t,0>e?0:0|checked(e)),!Buffer.TYPED_ARRAY_SUPPORT)for(var r=0;e>r;r++)t[r]=0;return t}function fromString(t,e,r){("string"!=typeof r||""===r)&&(r="utf8");var n=0|byteLength(e,r);return t=allocate(t,n),t.write(e,r),t}function fromObject(t,e){if(Buffer.isBuffer(e))return fromBuffer(t,e);if(isArray(e))return fromArray(t,e);if(null==e)throw new TypeError("must start with nu
 mber, buffer, array or string");if("undefined"!=typeof ArrayBuffer){if(e.buffer instanceof ArrayBuffer)return fromTypedArray(t,e);if(e instanceof ArrayBuffer)return fromArrayBuffer(t,e)}return e.length?fromArrayLike(t,e):fromJsonObject(t,e)}function fromBuffer(t,e){var r=0|checked(e.length);return t=allocate(t,r),e.copy(t,0,0,r),t}function fromArray(t,e){var r=0|checked(e.length);t=allocate(t,r);for(var n=0;r>n;n+=1)t[n]=255&e[n];return t}function fromTypedArray(t,e){var r=0|checked(e.length);t=allocate(t,r);for(var n=0;r>n;n+=1)t[n]=255&e[n];return t}function fromArrayBuffer(t,e){return e.byteLength,Buffer.TYPED_ARRAY_SUPPORT?(t=new Uint8Array(e),t.__proto__=Buffer.prototype):t=fromTypedArray(t,new Uint8Array(e)),t}function fromArrayLike(t,e){var r=0|checked(e.length);t=allocate(t,r);for(var n=0;r>n;n+=1)t[n]=255&e[n];return t}function fromJsonObject(t,e){var r,n=0;"Buffer"===e.type&&isArray(e.data)&&(r=e.data,n=0|checked(r.length)),t=allocate(t,n);for(var f=0;n>f;f+=1)t[f]=255&r[f
 ];return t}function allocate(t,e){Buffer.TYPED_ARRAY_SUPPORT?(t=new Uint8Array(e),t.__proto__=Buffer.prototype):t.length=e;var r=0!==e&&e<=Buffer.poolSize>>>1;return r&&(t.parent=rootParent),t}function checked(t){if(t>=kMaxLength())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+kMaxLength().toString(16)+" bytes");return 0|t}function SlowBuffer(t,e){if(!(this instanceof SlowBuffer))return new SlowBuffer(t,e);var r=new Buffer(t,e);return delete r.parent,r}function byteLength(t,e){"string"!=typeof t&&(t=""+t);var r=t.length;if(0===r)return 0;for(var n=!1;;)switch(e){case"ascii":case"binary":case"raw":case"raws":return r;case"utf8":case"utf-8":return utf8ToBytes(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return base64ToBytes(t).length;default:if(n)return utf8ToBytes(t).length;e=(""+e).toLowerCase(),n=!0}}function slowToString(t,e,r){var n=!1;if(e=0|e,r=void 0===r||r===1/0?this.length:0|r,t|
 |(t="utf8"),0>e&&(e=0),r>this.length&&(r=this.length),e>=r)return"";for(;;)switch(t){case"hex":return hexSlice(this,e,r);case"utf8":case"utf-8":return utf8Slice(this,e,r);case"ascii":return asciiSlice(this,e,r);case"binary":return binarySlice(this,e,r);case"base64":return base64Slice(this,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,e,r);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}function hexWrite(t,e,r,n){r=Number(r)||0;var f=t.length-r;n?(n=Number(n),n>f&&(n=f)):n=f;var i=e.length;if(i%2!==0)throw new Error("Invalid hex string");n>i/2&&(n=i/2);for(var o=0;n>o;o++){var u=parseInt(e.substr(2*o,2),16);if(isNaN(u))throw new Error("Invalid hex string");t[r+o]=u}return o}function utf8Write(t,e,r,n){return blitBuffer(utf8ToBytes(e,t.length-r),t,r,n)}function asciiWrite(t,e,r,n){return blitBuffer(asciiToBytes(e),t,r,n)}function binaryWrite(t,e,r,n){return asciiWrite(t,e,r,n)}function base64Write(t,e,r,n){retur
 n blitBuffer(base64ToBytes(e),t,r,n)}function ucs2Write(t,e,r,n){return blitBuffer(utf16leToBytes(e,t.length-r),t,r,n)}function base64Slice(t,e,r){return 0===e&&r===t.length?base64.fromByteArray(t):base64.fromByteArray(t.slice(e,r))}function utf8Slice(t,e,r){r=Math.min(t.length,r);for(var n=[],f=e;r>f;){var i=t[f],o=null,u=i>239?4:i>223?3:i>191?2:1;if(r>=f+u){var s,h,a,c;switch(u){case 1:128>i&&(o=i);break;case 2:s=t[f+1],128===(192&s)&&(c=(31&i)<<6|63&s,c>127&&(o=c));break;case 3:s=t[f+1],h=t[f+2],128===(192&s)&&128===(192&h)&&(c=(15&i)<<12|(63&s)<<6|63&h,c>2047&&(55296>c||c>57343)&&(o=c));break;case 4:s=t[f+1],h=t[f+2],a=t[f+3],128===(192&s)&&128===(192&h)&&128===(192&a)&&(c=(15&i)<<18|(63&s)<<12|(63&h)<<6|63&a,c>65535&&1114112>c&&(o=c))}}null===o?(o=65533,u=1):o>65535&&(o-=65536,n.push(o>>>10&1023|55296),o=56320|1023&o),n.push(o),f+=u}return decodeCodePointsArray(n)}function decodeCodePointsArray(t){var e=t.length;if(MAX_ARGUMENTS_LENGTH>=e)return String.fromCharCode.apply(String
 ,t);for(var r="",n=0;e>n;)r+=String.fromCharCode.apply(String,t.slice(n,n+=MAX_ARGUMENTS_LENGTH));return r}function asciiSlice(t,e,r){var n="";r=Math.min(t.length,r);for(var f=e;r>f;f++)n+=String.fromCharCode(127&t[f]);return n}function binarySlice(t,e,r){var n="";r=Math.min(t.length,r);for(var f=e;r>f;f++)n+=String.fromCharCode(t[f]);return n}function hexSlice(t,e,r){var n=t.length;(!e||0>e)&&(e=0),(!r||0>r||r>n)&&(r=n);for(var f="",i=e;r>i;i++)f+=toHex(t[i]);return f}function utf16leSlice(t,e,r){for(var n=t.slice(e,r),f="",i=0;i<n.length;i+=2)f+=String.fromCharCode(n[i]+256*n[i+1]);return f}function checkOffset(t,e,r){if(t%1!==0||0>t)throw new RangeError("offset is not uint");if(t+e>r)throw new RangeError("Trying to access beyond buffer length")}function checkInt(t,e,r,n,f,i){if(!Buffer.isBuffer(t))throw new TypeError("buffer must be a Buffer instance");if(e>f||i>e)throw new RangeError("value is out of bounds");if(r+n>t.length)throw new RangeError("index out of range")}function ob
 jectWriteUInt16(t,e,r,n){0>e&&(e=65535+e+1);for(var f=0,i=Math.min(t.length-r,2);i>f;f++)t[r+f]=(e&255<<8*(n?f:1-f))>>>8*(n?f:1-f)}function objectWriteUInt32(t,e,r,n){0>e&&(e=4294967295+e+1);for(var f=0,i=Math.min(t.length-r,4);i>f;f++)t[r+f]=e>>>8*(n?f:3-f)&255}function checkIEEE754(t,e,r,n,f,i){if(e>f||i>e)throw new RangeError("value is out of bounds");if(r+n>t.length)throw new RangeError("index out of range");if(0>r)throw new RangeError("index out of range")}function writeFloat(t,e,r,n,f){return f||checkIEEE754(t,e,r,4,3.4028234663852886e38,-3.4028234663852886e38),ieee754.write(t,e,r,n,23,4),r+4}function writeDouble(t,e,r,n,f){return f||checkIEEE754(t,e,r,8,1.7976931348623157e308,-1.7976931348623157e308),ieee754.write(t,e,r,n,52,8),r+8}function base64clean(t){if(t=stringtrim(t).replace(INVALID_BASE64_RE,""),t.length<2)return"";for(;t.length%4!==0;)t+="=";return t}function stringtrim(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function toHex(t){return 16>t?"0"+t.toString(
 16):t.toString(16)}function utf8ToBytes(t,e){e=e||1/0;for(var r,n=t.length,f=null,i=[],o=0;n>o;o++){if(r=t.charCodeAt(o),r>55295&&57344>r){if(!f){if(r>56319){(e-=3)>-1&&i.push(239,191,189);continue}if(o+1===n){(e-=3)>-1&&i.push(239,191,189);continue}f=r;continue}if(56320>r){(e-=3)>-1&&i.push(239,191,189),f=r;continue}r=(f-55296<<10|r-56320)+65536}else f&&(e-=3)>-1&&i.push(239,191,189);if(f=null,128>r){if((e-=1)<0)break;i.push(r)}else if(2048>r){if((e-=2)<0)break;i.push(r>>6|192,63&r|128)}else if(65536>r){if((e-=3)<0)break;i.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(1114112>r))throw new Error("Invalid code point");if((e-=4)<0)break;i.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return i}function asciiToBytes(t){for(var e=[],r=0;r<t.length;r++)e.push(255&t.charCodeAt(r));return e}function utf16leToBytes(t,e){for(var r,n,f,i=[],o=0;o<t.length&&!((e-=2)<0);o++)r=t.charCodeAt(o),n=r>>8,f=r%256,i.push(f),i.push(n);return i}function base64ToBytes(t){return base64.toByteArray(bas
 e64clean(t))}function blitBuffer(t,e,r,n){for(var f=0;n>f&&!(f+r>=e.length||f>=t.length);f++)e[f+r]=t[f];return f}var base64=require("base64-js"),ieee754=require("ieee754"),isArray=require("isarray");exports.Buffer=Buffer,exports.SlowBuffer=SlowBuffer,exports.INSPECT_MAX_BYTES=50,Buffer.poolSize=8192;var rootParent={};Buffer.TYPED_ARRAY_SUPPORT=void 0!==global.TYPED_ARRAY_SUPPORT?global.TYPED_ARRAY_SUPPORT:typedArraySupport(),Buffer.TYPED_ARRAY_SUPPORT?(Buffer.prototype.__proto__=Uint8Array.prototype,Buffer.__proto__=Uint8Array):(Buffer.prototype.length=void 0,Buffer.prototype.parent=void 0),Buffer.isBuffer=function(t){return!(null==t||!t._isBuffer)},Buffer.compare=function(t,e){if(!Buffer.isBuffer(t)||!Buffer.isBuffer(e))throw new TypeError("Arguments must be Buffers");if(t===e)return 0;for(var r=t.length,n=e.length,f=0,i=Math.min(r,n);i>f&&t[f]===e[f];)++f;return f!==i&&(r=t[f],n=e[f]),n>r?-1:r>n?1:0},Buffer.isEncoding=function(t){switch(String(t).toLowerCase()){case"hex":case"utf
 8":case"utf-8":case"ascii":case"binary":case"base64":case"raw":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},Buffer.concat=function(t,e){if(!isArray(t))throw new TypeError("list argument must be an Array of Buffers.");if(0===t.length)return new Buffer(0);var r;if(void 0===e)for(e=0,r=0;r<t.length;r++)e+=t[r].length;var n=new Buffer(e),f=0;for(r=0;r<t.length;r++){var i=t[r];i.copy(n,f),f+=i.length}return n},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.toString=function(){var t=0|this.length;return 0===t?"":0===arguments.length?utf8Slice(this,0,t):slowToString.apply(this,arguments)},Buffer.prototype.equals=function(t){if(!Buffer.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t?!0:0===Buffer.compare(this,t)},Buffer.prototype.inspect=function(){var t="",e=exports.INSPECT_MAX_BYTES;return this.length>0&&(t=this.toString("hex",0,e).match(/.{2}/g).join(" "),this.length>e&&(t+=" ... ")),"<Buffer "+t
 +">"},Buffer.prototype.compare=function(t){if(!Buffer.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t?0:Buffer.compare(this,t)},Buffer.prototype.indexOf=function(t,e){function r(t,e,r){for(var n=-1,f=0;r+f<t.length;f++)if(t[r+f]===e[-1===n?0:f-n]){if(-1===n&&(n=f),f-n+1===e.length)return r+n}else n=-1;return-1}if(e>2147483647?e=2147483647:-2147483648>e&&(e=-2147483648),e>>=0,0===this.length)return-1;if(e>=this.length)return-1;if(0>e&&(e=Math.max(this.length+e,0)),"string"==typeof t)return 0===t.length?-1:String.prototype.indexOf.call(this,t,e);if(Buffer.isBuffer(t))return r(this,t,e);if("number"==typeof t)return Buffer.TYPED_ARRAY_SUPPORT&&"function"===Uint8Array.prototype.indexOf?Uint8Array.prototype.indexOf.call(this,t,e):r(this,[t],e);throw new TypeError("val must be string, number or Buffer")},Buffer.prototype.write=function(t,e,r,n){if(void 0===e)n="utf8",r=this.length,e=0;else if(void 0===r&&"string"==typeof e)n=e,r=this.length,e=0;else if(isFinite
 (e))e=0|e,isFinite(r)?(r=0|r,void 0===n&&(n="utf8")):(n=r,r=void 0);else{var f=n;n=e,e=0|r,r=f}var i=this.length-e;if((void 0===r||r>i)&&(r=i),t.length>0&&(0>r||0>e)||e>this.length)throw new RangeError("attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return hexWrite(this,t,e,r);case"utf8":case"utf-8":return utf8Write(this,t,e,r);case"ascii":return asciiWrite(this,t,e,r);case"binary":return binaryWrite(this,t,e,r);case"base64":return base64Write(this,t,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,t,e,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},Buffer.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var MAX_ARGUMENTS_LENGTH=4096;Buffer.prototype.slice=function(t,e){var r=this.length;t=~~t,e=void 0===e?r:~~e,0>t?(t+=r,0>t&&(t=0)):t>r&&(t=r),0>e?(e+=r,0>e&&(e=0)):e>r&&(e=r),t>e&&(e=t);var n;if(Buffer.TYPED_ARRAY_SU
 PPORT)n=this.subarray(t,e),n.__proto__=Buffer.prototype;else{var f=e-t;n=new Buffer(f,void 0);for(var i=0;f>i;i++)n[i]=this[i+t]}return n.length&&(n.parent=this.parent||this),n},Buffer.prototype.readUIntLE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=this[t],f=1,i=0;++i<e&&(f*=256);)n+=this[t+i]*f;return n},Buffer.prototype.readUIntBE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=this[t+--e],f=1;e>0&&(f*=256);)n+=this[t+--e]*f;return n},Buffer.prototype.readUInt8=function(t,e){return e||checkOffset(t,1,this.length),this[t]},Buffer.prototype.readUInt16LE=function(t,e){return e||checkOffset(t,2,this.length),this[t]|this[t+1]<<8},Buffer.prototype.readUInt16BE=function(t,e){return e||checkOffset(t,2,this.length),this[t]<<8|this[t+1]},Buffer.prototype.readUInt32LE=function(t,e){return e||checkOffset(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},Buffer.prototype.readUInt32BE=function(t,e){return e||checkOffset(t,
 4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},Buffer.prototype.readIntLE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=this[t],f=1,i=0;++i<e&&(f*=256);)n+=this[t+i]*f;return f*=128,n>=f&&(n-=Math.pow(2,8*e)),n},Buffer.prototype.readIntBE=function(t,e,r){t=0|t,e=0|e,r||checkOffset(t,e,this.length);for(var n=e,f=1,i=this[t+--n];n>0&&(f*=256);)i+=this[t+--n]*f;return f*=128,i>=f&&(i-=Math.pow(2,8*e)),i},Buffer.prototype.readInt8=function(t,e){return e||checkOffset(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},Buffer.prototype.readInt16LE=function(t,e){e||checkOffset(t,2,this.length);var r=thi

<TRUNCATED>

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


[08/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js b/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
deleted file mode 100644
index e81463a..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
-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) {
-
-  /**
-   * @method SettingsController
-   * @param $scope
-   * @param QDRServer
-   *
-   * Controller that handles the QDR settings page
-   */
-
-  QDR.module.controller("QDR.SettingsController", ['$scope', 'QDRService', '$timeout', '$location', function($scope, QDRService, $timeout, $location) {
-
-    $scope.connecting = false;
-    $scope.connectionError = false;
-    $scope.connectionErrorText = undefined;
-    $scope.forms = {};
-
-    $scope.formEntity = angular.fromJson(localStorage[QDR.SETTINGS_KEY]) || {address: '', port: '', username: '', password: '', autostart: false};
-
-    $scope.$watch('formEntity', function(newValue, oldValue) {
-      if (newValue !== oldValue) {
-        localStorage[QDR.SETTINGS_KEY] = angular.toJson(newValue);
-      }
-    }, true);
-
-    $scope.buttonText = function() {
-      if (QDRService.isConnected()) {
-        return "Disconnect";
-      } else {
-        return "Connect";
-      }
-    };
-
-    $scope.connect = function() {
-		if (QDRService.connected) {
-			QDRService.disconnect();
-		return;
-		}
-
-		if ($scope.settings.$valid) {
-			$scope.connectionError = false;
-			$scope.connecting = true;
-			$timeout( doConnect )   // timeout so connecting animation can display
-		}
-	}
-
-	var doConnect = function () {
-        if (!$scope.formEntity.address)
-            $scope.formEntity.address = "localhost"
-
-        console.log("attempting to connect to " + $scope.formEntity.address + ':' + $scope.formEntity.port);
-	    QDRService.addDisconnectAction(function () {
-			$timeout( function () {
-				QDR.log.debug("disconnect action called");
-                $scope.connecting = false;
-                $scope.connectionErrorText = QDRService.errorText;
-                $scope.connectionError = true;
-        	})
-	    });
-        QDRService.addConnectAction(function() {
-          //QDR.log.debug("got connection notification");
-			$timeout( function () {
-				$scope.connecting = false;
-			})
-        });
-        QDRService.connect($scope.formEntity);
-      }
-
-  }]);
-
-
-QDR.module.directive('posint', function (){
-   return {
-	require: 'ngModel',
-
-	link: function(scope, elem, attr, ctrl) {
-		// input type number allows + and - but we don't want them so filter them out
-		elem.bind('keypress', function (event) {
-			var nkey = !event.charCode ? event.which : event.charCode;
-			var skey = String.fromCharCode(nkey);
-			var nono = "-+.,"
-			if (nono.indexOf(skey) >= 0) {
-				event.preventDefault();
-				return false;
-			}
-			// firefox doesn't filter out non-numeric input. it just sets the ctrl to invalid
-			if (/[\!\@\#\$\%^&*\(\)]/.test(skey) && event.shiftKey || // prevent shift numbers
-				!(                                      // prevent all but the following
-					nkey <= 0 ||                            // arrows
-					nkey == 8 ||                            // delete|backspace
-					nkey == 13 ||                           // enter
-					(nkey >= 37 && nkey <=40) ||            // arrows
-					event.ctrlKey || event.altKey ||        // ctrl-v, etc.
-					/[0-9]/.test(skey))                     // numbers
-				) {
-					event.preventDefault();
-					return false;
-			}
-		})
-		// check the current value of input
-		var _isPortInvalid = function (value) {
-			var port = value + ''
-			var isErrRange = false;
-			// empty string is valid
-			if (port.length !== 0) {
-				var n = ~~Number(port);
-				if (n < 1 || n > 65535) {
-					isErrRange = true;
-				}
-			}
-			ctrl.$setValidity('range', !isErrRange)
-			return isErrRange;
-		}
-
-		//For DOM -> model validation
-		ctrl.$parsers.unshift(function(value) {
-			return _isPortInvalid(value) ? undefined : value;
-		});
-
-		//For model -> DOM validation
-		ctrl.$formatters.unshift(function(value) {
-			_isPortInvalid(value);
-			return value;
-		});
-      }
-   };
-});
-
-  return QDR;
-}(QDR || {}));
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js b/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
new file mode 120000
index 0000000..6861eb2
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrSettings.js
\ No newline at end of file

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


[11/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
DISPATCH-316 Hawtio and stand-alone consoles now use common code


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

Branch: refs/heads/master
Commit: d98ae37ef5bed5080ea50b53eef737e914dd9be6
Parents: c9d1413
Author: Ernest Allen <ea...@redhat.com>
Authored: Fri Aug 19 12:57:59 2016 -0400
Committer: Ernest Allen <ea...@redhat.com>
Committed: Fri Aug 19 12:57:59 2016 -0400

----------------------------------------------------------------------
 .../src/main/webapp/plugin/css/dispatch.css     |  626 +----
 .../src/main/webapp/plugin/css/plugin.css       |  870 +------
 .../src/main/webapp/plugin/html/qdrCharts.html  |    4 +-
 .../src/main/webapp/plugin/html/qdrConnect.html |   81 +-
 .../src/main/webapp/plugin/html/qdrLayout.html  |   27 +-
 .../src/main/webapp/plugin/html/qdrList.html    |  112 +-
 .../main/webapp/plugin/html/qdrOverview.html    |  154 +-
 .../src/main/webapp/plugin/html/qdrSchema.html  |   22 +-
 .../main/webapp/plugin/html/qdrTopology.html    |  220 +-
 .../main/webapp/plugin/html/tmplListChart.html  |   12 +
 .../main/webapp/plugin/html/tmplListTree.html   |    8 +
 .../webapp/plugin/html/tmplOverviewTree.html    |    6 +
 .../src/main/webapp/plugin/js/dispatchPlugin.js |   58 +-
 .../hawtio/src/main/webapp/plugin/js/navbar.js  |  124 +-
 .../main/webapp/plugin/js/qdrChartService.js    | 1112 +--------
 .../src/main/webapp/plugin/js/qdrCharts.js      |   38 +-
 .../hawtio/src/main/webapp/plugin/js/qdrList.js |  812 +------
 .../src/main/webapp/plugin/js/qdrListChart.js   |    1 +
 .../src/main/webapp/plugin/js/qdrOverview.js    | 1427 +-----------
 .../src/main/webapp/plugin/js/qdrSchema.js      |   73 +-
 .../src/main/webapp/plugin/js/qdrService.js     |  889 +-------
 .../src/main/webapp/plugin/js/qdrSettings.js    |  152 +-
 .../src/main/webapp/plugin/js/qdrTopology.js    | 2133 +-----------------
 .../src/main/webapp/plugin/lib/rhea-min.js      |    8 +-
 console/stand-alone/index.html                  |   54 +-
 console/stand-alone/plugin/css/dispatch.css     |  640 ++++++
 console/stand-alone/plugin/css/plugin.css       |  439 +++-
 console/stand-alone/plugin/css/qdrTopology.css  |  562 -----
 console/stand-alone/plugin/css/site-base.css    |  229 +-
 console/stand-alone/plugin/html/qdrCharts.html  |  114 +-
 console/stand-alone/plugin/html/qdrConnect.html |   90 +-
 console/stand-alone/plugin/html/qdrLayout.html  |    8 +-
 console/stand-alone/plugin/html/qdrList.html    |   96 +-
 .../stand-alone/plugin/html/qdrOverview.html    |   96 +-
 console/stand-alone/plugin/html/qdrSchema.html  |    4 +-
 .../stand-alone/plugin/html/qdrTopology.html    |  121 +-
 .../stand-alone/plugin/html/tmplListChart.html  |   18 +
 .../stand-alone/plugin/html/tmplListTree.html   |   17 +
 .../plugin/html/tmplOverviewTree.html           |   15 +
 console/stand-alone/plugin/js/dispatchPlugin.js |  254 +++
 console/stand-alone/plugin/js/navbar.js         |   65 +-
 .../stand-alone/plugin/js/qdrChartService.js    |  304 ++-
 console/stand-alone/plugin/js/qdrCharts.js      |  212 +-
 console/stand-alone/plugin/js/qdrList.js        |  879 +++++---
 console/stand-alone/plugin/js/qdrListChart.js   |  109 +
 console/stand-alone/plugin/js/qdrOverview.js    | 1559 +++++++++----
 console/stand-alone/plugin/js/qdrPlugin.js      |  197 --
 console/stand-alone/plugin/js/qdrSchema.js      |   45 +-
 console/stand-alone/plugin/js/qdrService.js     |  267 ++-
 console/stand-alone/plugin/js/qdrSettings.js    |  142 +-
 console/stand-alone/plugin/js/qdrTopology.js    |  827 ++++---
 console/stand-alone/plugin/lib/rhea-min.js      |   99 +-
 52 files changed, 4852 insertions(+), 11579 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/css/dispatch.css
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/css/dispatch.css b/console/hawtio/src/main/webapp/plugin/css/dispatch.css
deleted file mode 100644
index cb050e6..0000000
--- a/console/hawtio/src/main/webapp/plugin/css/dispatch.css
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
-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 .gridStyle {
-    height: inherit !important;
-	min-height: initial !important;
-	overflow: initial;
-}
-
-div#multiple_details, div#link_details {
-	height: 300px;
-	width: 600px;
-	display: none;
-	padding: 0.5em;
-    border: 1px solid;
-	position: absolute;
-	background-color: white;
-	max-height: 330px !important;
-    overflow: hidden;
-}
-div#multiple_details div.ngRow.selected {
-	background-color: #c9dde1 !important;
-}
-
-div.grid-values {
-	text-align: right;
-}
-
-div.grid-values.ngCellText span {
-	padding-right: 4px;
-}
-
-.panel-adjacent {
-  margin-left: 430px;
-}
-
-#topologyForm.selected {
-  border: 1px solid red;
-}
-#topologyForm {
-    border: 1px solid white;
-    padding: 2px;
-    /* position: relative; */
-    /* top: -8px; */
-}
-div.qdr-topology.pane.left .ngViewport {
-    /* border: 1px solid lightgray; */
-}
-
-#topologyForm > div {
-  width:396px;
-  /*height: 100vh;*/
-}
-
-/* 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:10px; */
-	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;
-}
-
-#crosssection {
-    display: none;
-    position: absolute;
-    top: 200px;
-    left: 600px;
-}
-
-.node circle {
-/*  fill: rgb(31, 119, 180);
-  fill-opacity: .25; */
-  fill: #cfe2f3;
-  fill-opacity: .98;
-  stroke: black;
-  stroke-width: 3px;
-}
-
-circle.subcircle {
-    stroke-width: 1px;
-    /* stroke-dasharray: 2; */
-    fill-opacity: 0;
-    stroke: darkgray;
-}
-
-.leaf circle {
-  fill: #6fa8dc;
-  fill-opacity: 0.95;
-  stroke-width: 3px;
-}
-
-.leaf circle[title] {
-    font-family: monospace;
-
-}
-
-#svg_legend {
-    position: absolute;
-    top: 110px;
-    right: 0;
-    border: 1px solid #ccc;
-    border-radius: 5px;
-    background-color: #fcfcfc;
-    margin-right: 1.3em;
-	padding: 1em;
-}
-
-#svg_legend svg {
-    height: 235px;
-    width: 180px;
-}
-
-#multiple_details div.gridStyle {
-/*	height: 50em; */
-	min-height: 70px !important;
-	height: auto !important;
-}
-
-#multiple_details .ngViewport {
-    height: auto !important;
-}
-
-#multiple_details .gridCellButton button, #link_details .gridCellButton button {
-    margin: .25em .4em;
-    font-size: 12px;
-    height: 2em;
-	padding-top: .1em;
-}
-
-#linkFilter {
-	display: none;
-	padding: 0.5em;
-	border: 1px solid grey;
-	background-color: #F0F0F0;
-	position: absolute;
-}
-
-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;
-}
-
-.links button.btn-filter {
-	padding: 0 1em 0 0;
-    margin-left: 1em;
-    font-size: 1em;
-}
-
-button.btn-filter {
-	visibility: hidden;
-}
-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;
-}
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/css/dispatch.css b/console/hawtio/src/main/webapp/plugin/css/dispatch.css
new file mode 120000
index 0000000..f3dbfa0
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/css/dispatch.css
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/css/dispatch.css
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/css/plugin.css
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/css/plugin.css b/console/hawtio/src/main/webapp/plugin/css/plugin.css
deleted file mode 100644
index eeeb558..0000000
--- a/console/hawtio/src/main/webapp/plugin/css/plugin.css
+++ /dev/null
@@ -1,869 +0,0 @@
-/*
-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: 44px !important;
-}
-
-div.charts-header {
-  font-size: 1.2em;
-  color: #666666;
-  margin: 1em 0;
-}
-
-.selectedNode, .selectedAction, .selectedEntity {
-    font-weight: 600;
-    color: #606066;
-}
-
-.okButton {
-    text-align: center;
-    margin: 1em;
-}
-
-span.showChartsLink {
-    border: 1px solid blue;
-    padding: 1px 2px;
-}
-
-div.listGraphs p {
-    margin: 1em 0 2em 2em;
-    text-align: center;
-}
-
-div.centered {
-    text-align: center;
-    margin: 4em;
-}
-
-.modal-body.centered {
-	margin: 0;
-}
-
-/* dialog */
-div.aChart {
-    height: 200px;
-    width:  400px;
-    margin: 1em;
-}
-
-/* dashboard */
-div.aChart.hDash {
-	/* width: 21em; */
-	/* height: 17em; */
-	width: 100%;
-	height: 87%;
-	margin: 0;
-	padding: 0;
-
-}
-div.chartContainer {
-    float: left;
-	width: 100%;
-	height: 100%;
-	overflow: hidden;
-}
-
-/* the x and y axis lines */
-.d3Chart g.axis path.domain {
-    stroke-width: 1;
-    stroke: black;
-}
-
-/* the line surrounding the area chart */
-div.d3Chart path {
-/*    stroke: black; */
-    stroke-width: 0;
-/*	opacity: 0.5; */
-}
-
-/* the line above the area chart */
-/* the color gets overridden */
-div.d3Chart path.line {
-    stroke: steelblue;
-    stroke-width: 1.5;
-    fill: none;
-    opacity: 1;
-}
-
-.mo-rect {
-    fill: #ffffdd;
-    stroke: #f0f0f0;
-    stroke-width: 1;
-}
-
-.mo-guide {
-    fill: none;
-    stroke: #d0d0d0;
-    stroke-width: 2;
-    stroke-dasharray: 3,3;
-}
-
-div.d3Chart .title {
-    text-decoration: underline;
-}
-
-
-.axis line, .axis path {
-  fill: none;
-  shape-rendering: crispEdges;
-  stroke-width: 1;
-  stroke: #000000;
-}
-
-.axis line {
-  stroke: #C0C0C0;
-  stroke-dasharray: 1,1;
-  opacity: 0.5;
-}
-
-.y.axis text, .x.axis text, .focus text, div.d3Chart .title {
-    font-size: 12px;
-}
-
-.y.axis path {
-   stroke: #000;
- }
-
-.overlay {
-   fill: none;
-   pointer-events: all;
- }
-
-.focus circle {
-   fill: none;
-   stroke: steelblue;
- }
-.focus .fo-table {
-	/* box-shadow: 2px 2px 3px #EEE; */
-}
-
-div.d3Chart {
-    padding: 1em 0;
-    border: 1px solid #C0C0C0;
-}
-div.d3Chart.hDash {
-    border: 0px;
-}
-
-div.d3Chart .axis path {
-	display: inherit;
-}
-.c3-circle {
-	display: none;
-}
-
-.fo-table {
-	border: 1px solid darkgray;
-	background-color: white;
-	font-size: .85em;
-}
-
-.fo-table td {
-	padding: 4px;
-	border-left: 1px solid darkgray;
-}
-.fo-table tr.detail td {
-	padding: 1px 4px;
-}
-.fo-title {
-	color: white;
-	background-color: darkgray;
-}
-
-.fo-table-legend {
-	width: 8px;
-	height: 8px;
-	border: 1px solid black;
-	margin: 0 4px;
-	display: inline-block;
-}
-
-svg .legend {
-	dominant-baseline: central;
-}
-
-div.chartContainer div.aChart {
-    margin-top: 0.5em;
-}
-
-div.qdr-attributes .tree-header select {
-	width: 100%;
-}
-
-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-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.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: 100px;
-}
-.qdr-topology.pane.left {
-	top: 104px;
-}
-.qdr-overview.pane.left {
-	left: 10px;
-}
-
-.treeContainer {
-	/*width: 250px;
-	float: left;
-	overflow-y: auto;
-	border-right: 1px solid lightgrey;
-	height: 100vh;
-*/
-}
-
-#entityNames {
-    width: 20em;
-    float: left;
-}
-
-.treeDetails {
-	/* margin-left: 300px; */
-}
-
-.gridStyle:not(.noHighlight) .ui-grid-row:hover .ui-grid-cell-contents {
-	background-color: #e0e0ff;
-}
-
-.ngCellText {
-	padding: 4px 0 0 4px;
-}
-
-.overview {
-	border-bottom: 1px solid #d4d4d4;
-}
-
-.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).connection.external .dynatree-icon:before,
-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: "\f152";
-}
-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).router\.node .dynatree-icon:before {
-     content: "\f013";
-}
-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\.link.inter-router .dynatree-icon:before {
-     content: "\f04e";
-}
-span:not(.dynatree-has-children).router\.link.endpoint .dynatree-icon:before {
-     content: "\f051";
-}
-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).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;
-}
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/css/plugin.css b/console/hawtio/src/main/webapp/plugin/css/plugin.css
new file mode 120000
index 0000000..b94a926
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/css/plugin.css
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/css/plugin.css
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrCharts.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrCharts.html b/console/hawtio/src/main/webapp/plugin/html/qdrCharts.html
index 3267a9d..cb4f338 100644
--- a/console/hawtio/src/main/webapp/plugin/html/qdrCharts.html
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrCharts.html
@@ -55,10 +55,10 @@ under the License.
             <tab ng-hide="$parent.chart.aggregate()" heading="Colors">
                 <legend>Chart colors</legend>
                 <div class="colorPicker">
-                    <div class="colorText">Area ({{dialogChart.areaColor}}):</div><div hawtio-color-picker="dialogChart.areaColor"></div>
+                    <label>Line: <input id="lineColor" name="lineColor" type="color" /></label>
                 </div>
                 <div class="colorPicker">
-                    <div class="colorText">Line ({{dialogChart.lineColor}}):</div><div hawtio-color-picker="dialogChart.lineColor"></div>
+                    <label>Area: <input id="areaColor" name="areaColor" type="color" /></label>
                 </div>
                 <div style="clear:both;"> </div>
             </tab>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html b/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html
deleted file mode 100644
index 54687fb..0000000
--- a/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<div class="row-fluid" ng-controller="QDR.SettingsController">
-      <div class="login container" ng-hide="connecting">
-          <div class="row" id="dispatch-login-container">
-              <div class="connect-column">
-                  <div class="alert alert-success">
-                      <p>
-                          Enter the address and port of a <strong><a href="http://qpid.apache.org/components/dispatch-router/" target="_blank">Qpid Dispatch Router</a></strong> to connect..
-                      </p>
-
-                      <p>
-                          The port should be a websockets <==> tcp proxy.
-                      </p>
-
-                      <p>
-                          When Autostart is checked, you will be automatically logged in to the router the next time you start the console.
-                      </p>
-
-                  </div>
-              </div>
-              <div class="connect-column connect-form">
-                  <form class="hawtio-form form-horizontal no-bottom-margin" novalidate ng-submit="connect()" name="settings">
-                      <fieldset>
-                          <div class="spacer"></div>
-                          <div class="control-group" ng-show="true">
-                              <label class="strong control-label">Address: </label>
-                              <div class="controls">
-                                  <input tabindex="1" type="text" ng-model="formEntity.address" placeholder="localhost" name="address" autofocus="autofocus" class="ng-pristine ng-valid ng-valid-required"></div>
-                          </div>
-                          <div class="control-group" ng-show="true">
-                              <label tabindex="-1" class="control-label" title="Ports to connect to, by default 5673">Port: </label>
-                              <div class="controls">
-                                  <input tabindex="2" posint type="number" placeholder="5673" tooltip="Ports to connect to, by default 5673" ng-model="formEntity.port" name="port" title="Ports to connect to, by default 5673">
-                                  <span ng-show="settings.port.$error.range">Must be 1 through 65535</span>
-                              </div>
-                          </div>
-                          <div class="control-group" ng-show="true">
-                              <label tabindex="-1" class="control-label" title="Whether or not the connection should be started as soon as you log into hawtio">Autostart: </label>
-                              <div class="controls">
-                                  <input tabindex="3" type="checkbox" tooltip="Whether or not the connection should be started as soon as you log into hawtio" ng-model="formEntity.autostart" name="autostart" title="Whether or not the connection should be started as soon as you log into hawtio"
-                                         class="ng-scope ng-pristine ng-valid"></div>
-                          </div>
-                          <input tabindex="-1" type="submit" style="position: absolute; left: -9999px; width: 1px; height: 1px;">
-                          <p></p>
-                          <div>
-                              <button type="submit" tabindex="4" class="btn btn-primary pull-right" ng-disabled="settings.$invalid">{{buttonText()}}</button>
-                              <!-- <button tabindex="-1" class="btn btn-secondary pull-right" ng-click="connect1()">Debug</button> -->
-                          </div>
-                      </fieldset>
-                  </form>
-              </div>
-          </div>
-      </div>
-
-      <div class="centered" ng-show="connecting">
-        <i class="icon-spin icon-spinner icon-4x"></i>
-        <p>Please wait, connecting now...</p>
-      </div>
-      <div class="centered" ng-show="connectionError">
-        <p>There was a connection error: {{connectionErrorText}}</p>
-      </div>
-
-</div>
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html b/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html
new file mode 120000
index 0000000..2eb3530
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrConnect.html
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/html/qdrConnect.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html b/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html
deleted file mode 100644
index 80cd5ea..0000000
--- a/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-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.
--->
-<ul class="nav nav-tabs connected dispatch-view" ng-controller="QDR.NavBarController">
-  <li ng-repeat="link in breadcrumbs" title="{{link.title}}" ng-show="isValid(link)" ng-class='{active : isActive(link.href), "pull-right" : isRight(link)}'>
-    <a ng-href="{{link.href}}" ng-bind-html-unsafe="link.content"></a>
-  </li>
-</ul>
-<div class="row-fluid dispatch-router">
-  <div ng-view></div>
-</div>
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html b/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html
new file mode 120000
index 0000000..f4caaf6
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrLayout.html
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/html/qdrLayout.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrList.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrList.html b/console/hawtio/src/main/webapp/plugin/html/qdrList.html
deleted file mode 100644
index e259e64..0000000
--- a/console/hawtio/src/main/webapp/plugin/html/qdrList.html
+++ /dev/null
@@ -1,111 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<div ng-controller="QDR.ListController">
-    <hawtio-pane class="qdr-attributes" position="left" width="200">
-        <div class="treeContainer">
-            <div class="tree-header"><select ng-options="node as node.name for node in nodes" ng-model="currentNode" ng-change="selectNode(currentNode)"></select></div>
-            <div id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div>
-            <div ng-init="treeReady()"></div>
-        </div>
-    </hawtio-pane>
-    <div class="row-fluid qdrListActions">
-        <ul class="nav nav-tabs">
-            <li ng-repeat="mode in modes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html-unsafe="mode.content"> </li>
-        </ul>
-        <h4>{{selectedRecordName}}</h4>
-        <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
-            <div ng-show="selectedRecordName === selectedEntity" class="no-content">There are no {{selectedEntity | safePlural}}</div>
-            <div ng-hide="selectedRecordName === selectedEntity" ng-grid="details"></div>
-        </div>
-        <div ng-show="currentMode.id === 'delete'">
-            <div class="delete" ng-show="selectedRecordName !== selectedEntity">
-                <button class="btn btn-primary" ng-click="remove()">Delete</button> {{selectedRecordName}}
-            </div>
-            <div ng-hide="selectedRecordName !== selectedEntity">
-                There are no {{selectedEntity}}s
-            </div>
-        </div>
-        <div class="operations" ng-show="currentMode.id === 'operations'">
-            <fieldset ng-show="operation != ''">
-                <table>
-                    <tr>
-                        <th>Attribute</th>
-                        <th>Value</th>
-                    </tr>
-                <tr title="{{attribute.title}}" ng-repeat="attribute in detailFields">
-                    <td><label for="{{attribute.name}}">{{attribute.name | humanify}}</label></td>
-                    <!-- we can't do <input type="{angular expression}"> because... jquery throws an exception because... -->
-                    <td>
-                    <div ng-if="attribute.input == 'input'">
-                        <!-- ng-pattern="testPattern(attribute)" -->
-                        <input ng-if="attribute.type == 'number'" type="number" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.rawValue" 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.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/>
-                        <textarea ng-if="attribute.type == 'textarea'" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"></textarea>
-                        <span ng-if="attribute.type == 'disabled'" >{{getAttributeValue(attribute)}}</span>
-                    </div>
-                    <div ng-if="attribute.input == 'select'">
-                        <select id="{{attribute.name}}" ng-model="attribute.selected" ng-options="item for item in attribute.rawtype track by item"></select>
-                    </div>
-                    <div ng-if="attribute.input == 'boolean'" class="boolean">
-                        <label><input type="radio" ng-model="attribute.rawValue" ng-value="true"> True</label>
-                        <label><input type="radio" ng-model="attribute.rawValue" ng-value="false"> False</label>
-                    </div>
-                    </td>
-                </tr>
-                <tr><td></td><td><button class="btn btn-primary" type="button" ng-click="ok()">{{operation | Pascalcase}}</button></td></tr>
-                </table>
-            </fieldset>
-        </div>
-        <div ng-show="currentMode.id === 'log'">
-            <table class="log-entry" ng-repeat="entry in logResults track by $index">
-                <tr>
-                    <td align="left" colspan="2">{{entry.time}}</td>
-                </tr>
-                <tr>
-                    <td>Type</td><td>{{entry.type}}</td>
-                </tr>
-                <tr>
-                    <td>Source</td><td>{{entry.source}}:{{entry.line}}</td>
-                </tr>
-                <tr>
-                    <td valign="middle">Message</td><td valign="middle"><pre>{{entry.message}}</pre></td>
-                </tr>
-            </table>
-        </div>
-    </div>
-</div>
-
-<!--
-    This is the template for the graph dialog that is displayed.
--->
-<script type="text/ng-template" id="template-from-script.html">
-    <div class="modal-header">
-        <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
-    </div>
-    <div class="modal-body">
-            <div id="{{svgDivId}}" class="d3Chart"></div>
-    </div>
-    <div class="modal-footer">
-        <span>
-            <div class="btn btn-success hdash-button" ng-click="addHChart()" title="Add this chart to a dashboard"><a ng-href="{{addToDashboardLink()}}"><i class="icon-share"></i> Add this chart to a dashboard</a></div>
-        </span>
-        <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
-    </div>
-</script>
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrList.html b/console/hawtio/src/main/webapp/plugin/html/qdrList.html
new file mode 120000
index 0000000..843a1ef
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrList.html
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/html/qdrList.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
deleted file mode 100644
index 14653b1..0000000
--- a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
+++ /dev/null
@@ -1,153 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<!--
-<div ng-controller="QDR.OverviewController">
-
-    <div class="treeContainer">
-        <div id="overtree"></div>
-    </div>
-
-    <div class="treeDetails" ng-include="template.url"></div>
-</div>
--->
-<div ng-controller="QDR.OverviewController">
-    <hawtio-pane class="qdr-overview" position="left" width="300">
-        <div class="treeContainer">
-            <div id="overtree"></div>
-        </div>
-    </hawtio-pane>
-    <div class="treeDetails" ng-include="template.url"></div>
-    <div id="linkFilter">
-        <div class="filter-title">
-            <h6>Filter by link type</h6>
-            <button ng-click="filterClose()" type="button" class="btn btn-primary filter-close">x</button>
-        </div>
-        <select ng-init="currentLinkFilter = 'endpoint'" ng-model="currentLinkFilter" ng-change="setLinkFilter(currentLinkFilter)">
-            <option value="">All link types</option>
-            <option value="endpoint" selected>Endpoints only</option>
-        </select>
-    </div>
-</div>
-
-<!-- the following scripts are content that gets loaded into the above div that has the temple.url -->
-<script type="text/ng-template" id="routers.html">
-    <div class="row-fluid">
-        <h3>Routers</h3>
-        <div class="overview">
-            <div class="gridStyle" ng-grid="allRouters"></div>
-        </div>
-    </div>
-</script>
-
-<script type="text/ng-template" id="router.html">
-    <div class="row-fluid">
-        <h3>Router {{router.data.title}} attributes</h3>
-        <div class="gridStyle noHighlight" ng-grid="routerGrid"></div>
-    </div>
-</script>
-
-<script type="text/ng-template" id="addresses.html">
-    <div class="row-fluid">
-        <h3>Addresses</h3>
-        <div class="overview">
-            <div class="gridStyle" ng-grid="addressesGrid"></div>
-        </div>
-    </div>
-</script>
-<script type="text/ng-template" id="address.html">
-    <div class="row-fluid">
-        <h3>Address {{address.data.title}}</h3>
-        <div class="gridStyle noHighlight" ng-grid="addressGrid"></div>
-    </div>
-</script>
-<script type="text/ng-template" id="links.html">
-    <div class="row-fluid">
-        <h3>Links</h3>
-        <div class="gridStyle" ng-grid="linksGrid"></div>
-    </div>
-</script>
-<script type="text/ng-template" id="link.html">
-    <div class="row-fluid">
-        <h3>Link {{link.data.title}}</h3>
-        <div class="gridStyle noHighlight" ng-grid="linkGrid"></div>
-    </div>
-</script>
-<script type="text/ng-template" id="connections.html">
-    <div class="row-fluid">
-    <h3>Connections</h3>
-    <div class="overview">
-        <div class="gridStyle" ng-grid="allConnectionGrid"></div>
-    </div>
-    </div>
-</script>
-<script type="text/ng-template" id="connection.html">
-    <div class="row-fluid">
-        <ul class="nav nav-tabs">
-            <li ng-repeat="mode in connectionModes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html-unsafe="mode.content"> </li>
-        </ul>
-        <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
-            <h3>Connection {{connection.data.title}}</h3>
-            <div class="gridStyle noHighlight" ng-grid="connectionGrid"></div>
-        </div>
-        <div ng-show="currentMode.id === 'links'" class="selectedItems">
-            <h3>External links for connection {{connection.data.title}}</h3>
-            <!-- <button title="{{quiesceState.buttonText}} links" type="button" ng-hide="quiesceHide()" ng-class="quiesceClass()" class="btn" ng-click="quiesceAllClicked()" ng-disabled="quiesceState.buttonDisabled">{{quiesceState.buttonText}}</button> -->
-            <div class="gridStyle noHighlight" ng-grid="connectionLinksGrid"></div>
-        </div>
-    </div>
-</script>
-<script type="text/ng-template" id="titleHeaderCellTemplate.html">
-    <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
-        <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
-        <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
-        <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
-        <div class="ngSortPriority">{{col.sortPriority}}</div>
-    </div>
-</script>
-<script type="text/ng-template" id="titleCellTemplate.html">
-    <div title="{{row.entity[col.field]}}" class="ngCellText">{{row.entity[col.field]}}</div>
-</script>
-<script type="text/ng-template" id="logs.html">
-    <div class="row-fluid">
-    <h3>Logs</h3>
-    </div>
-</script>
-<script type="text/ng-template" id="log.html">
-    <div class="row-fluid">
-    <h3>Log {{log.data.title}}</h3>
-    </div>
-</script>
-
-<script type="text/ng-template" id="linkRowTemplate.html">
-    <div ng-class="{linkDirIn: row.getProperty('linkDir') == 'in', linkDirOut: row.getProperty('linkDir') == 'out'}">
-        <div ng-style="{ 'cursor': row.cursor }" ng-repeat="col in renderedColumns" ng-class="col.colIndex()" class="ngCell {{col.cellClass}}">
-            <div class="ngVerticalBar" ng-style="{height: rowHeight}" ng-class="{ ngVerticalBarVisible: !$last }">&nbsp;</div>
-            <div ng-cell></div>
-        </div>
-    </div>
-</script>
-
-<script type="text/ng-template" id="linkAggTemplate.html">
-    <div ng-click='row.toggleExpand(); saveGroupState()' ng-style='rowStyle(row)' class='ngAggregate ng-scope' style='top: 0px; height: 48px; left: 0px;'>
-    <span class='ngAggregateText ng-binding'>
-            {{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})
-        </span>
-    <div ng-class="{true: 'ngAggArrowCollapsed', false: 'ngAggArrowExpanded'}[row.collapsed]"></div>
-    </div>
-</script>
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
new file mode 120000
index 0000000..3c33d8a
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrOverview.html
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/html/qdrOverview.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html b/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html
deleted file mode 100644
index 6661baf..0000000
--- a/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<div class="main-display row-fluid" ng-controller="QDR.SchemaController">
-    <div id="schema"></div>
-</div>
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html b/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html
new file mode 120000
index 0000000..7f9422e
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrSchema.html
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/html/qdrSchema.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html b/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
deleted file mode 100644
index 864441f..0000000
--- a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
+++ /dev/null
@@ -1,219 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<div class="qdrTopology row-fluid" ng-controller="QDR.TopologyController">
-    <div class="qdr-topology pane left" ng-controller="QDR.TopologyFormController">
-        <div id="topologyForm" ng-class="{selected : isSelected()}">
-            <!-- <div ng-repeat="form in forms" ng-show="isVisible(form)" ng-class='{selected : isSelected(form)}'> -->
-            <div ng-show="form == 'router'">
-                <h4>Router Info</h4>
-                <div class="gridStyle" ng-grid="topoGridOptions"></div>
-            </div>
-            <div ng-show="form == 'connection'">
-                <h4>Connection Info</h4>
-                <div class="gridStyle" ng-grid="topoGridOptions"></div>
-            </div>
-            <div id="addNodeForm" ng-show="form == 'add'">
-                <h4>Add a new router</h4>
-                <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 class="panel-adjacent">
-
-<!--
-        <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="crosssection"><!-- d3 pack here --></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">
-            <div class="gridStyle" ng-grid="multiDetails"></div>
-         </div>
-        <div id="link_details">
-            <div class="gridStyle" ng-grid="linkDetails"></div>
-        </div>
-    </div>
-</div>
-
-
-<script type="text/ng-template" id="titleHeaderCellTemplate.html">
-    <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
-        <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
-        <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
-        <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
-        <div class="ngSortPriority">{{col.sortPriority}}</div>
-    </div>
-</script>
-<script type="text/ng-template" id="titleCellTemplate.html">
-    <div title="{{row.entity[col.field]}}" class="ngCellText">{{row.entity[col.field]}}</div>
-</script>
-
-<!--
-    This is the template for the node edit dialog that is displayed.
--->
-<script type="text/ng-template" id="node-config-template.html">
-    <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>
-</script>
-
-<script type="text/ng-template" id="config-file-header.html">##
-## 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
-##
-</script>
-
-<script type="text/ng-template" id="download-dialog-template.html">
-    <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>
-
-<!--
-
-    <div title="Configure new router">
-
-        <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 class="okButton">
-            <button ng-click="done()">Done</button>
-        </div>
-
-    </div>
--->
-</script>
-
diff --git a/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html b/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
new file mode 120000
index 0000000..9dc2f21
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/qdrTopology.html
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/html/qdrTopology.html
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/tmplListChart.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/tmplListChart.html b/console/hawtio/src/main/webapp/plugin/html/tmplListChart.html
new file mode 100644
index 0000000..4a4811e
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/tmplListChart.html
@@ -0,0 +1,12 @@
+<div class="modal-header">
+    <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
+</div>
+<div class="modal-body">
+    <div id="{{svgDivId}}" class="d3Chart"></div>
+</div>
+<div class="modal-footer">
+        <span>
+            <div class="btn btn-success hdash-button" ng-click="addHChart()" title="Add this chart to a dashboard"><a ng-href="{{addToDashboardLink()}}"><i class="icon-share"></i> Add this chart to a dashboard</a></div>
+        </span>
+    <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/tmplListTree.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/tmplListTree.html b/console/hawtio/src/main/webapp/plugin/html/tmplListTree.html
new file mode 100644
index 0000000..b523efe
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/tmplListTree.html
@@ -0,0 +1,8 @@
+<hawtio-pane class="qdr-attributes" position="left" width="200">
+    <div class="treeContainer">
+        <div class="tree-header"><select ng-options="node as node.name for node in nodes" ng-model="currentNode" ng-change="selectNode(currentNode)"></select></div>
+        <div id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div>
+        <div ng-init="treeReady()"></div>
+    </div>
+</hawtio-pane>
+<ng-include src="'listGrid.html'"></ng-include>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/html/tmplOverviewTree.html
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/html/tmplOverviewTree.html b/console/hawtio/src/main/webapp/plugin/html/tmplOverviewTree.html
new file mode 100644
index 0000000..48f9cbc
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/html/tmplOverviewTree.html
@@ -0,0 +1,6 @@
+<hawtio-pane class="qdr-overview" position="left" width="300">
+    <div class="treeContainer">
+        <div id="overtree"></div>
+    </div>
+</hawtio-pane>
+<ng-include src="'overviewGrid.html'"></ng-include>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js b/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js
index 80c2c9d..9383f06 100644
--- a/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js
+++ b/console/hawtio/src/main/webapp/plugin/js/dispatchPlugin.js
@@ -82,6 +82,12 @@ var QDR = (function(QDR) {
 			.when(QDR.pluginRoot, {
 				templateUrl: QDR.templatePath + 'qdrConnect.html'
 			})
+			.when(QDR.pluginRoot + '/', {
+				templateUrl: QDR.templatePath + 'qdrConnect.html'
+			})
+			.when(QDR.pluginRoot + '/connect', {
+				templateUrl: QDR.templatePath + 'qdrConnect.html'
+			})
 			.when(QDR.pluginRoot + '/overview', {
 				templateUrl: QDR.templatePath + 'qdrOverview.html'
 			})
@@ -97,9 +103,6 @@ var QDR = (function(QDR) {
 			.when(QDR.pluginRoot + '/charts', {
 				templateUrl: QDR.templatePath + 'qdrCharts.html'
 			})
-			.when(QDR.pluginRoot + '/connect', {
-				templateUrl: QDR.templatePath + 'qdrConnect.html'
-			})
       })
 	  .config(function ($compileProvider) {
 			var cur = $compileProvider.urlSanitizationWhitelist();
@@ -111,6 +114,7 @@ var QDR = (function(QDR) {
 	  })
 	  .filter('to_trusted', function($sce){
 			return function(text) {
+			debugger;
 				return $sce.trustAsHtml(text);
 			};
       })
@@ -165,18 +169,22 @@ var QDR = (function(QDR) {
    *     plugin.  This is just a matter of adding to the workspace's
    *     topLevelTabs array.
    */
-  QDR.module.run(function(workspace, viewRegistry, layoutFull, $rootScope, $location, localStorage, QDRService, QDRChartService) {
+  QDR.module.run(function(workspace, viewRegistry, layoutFull, $route, $rootScope, $location, localStorage, QDRService, QDRChartService) {
 		QDR.log.info("*************creating Dispatch Console************");
 		var curPath = $location.path()
-		var lastLocation = localStorage[QDR.LAST_LOCATION];
-		if (!angular.isDefined(lastLocation)) {
-			lastLocation = "connect";
-		}
+		QDR.log.info("curPath is " + curPath)
+		var lastLocation = localStorage[QDR.LAST_LOCATION] || "connect"
 		if (lastLocation.startsWith(QDR.pluginRoot)) {
 			lastLocation = lastLocation.substr(QDR.pluginRoot.length+1)
+			if (lastLocation === '')
+				lastLocation = "overview"
 		}
+		$location.path(QDR.pluginRoot + "/connect");
 		if (curPath.startsWith(QDR.pluginRoot)) {
 			var org = curPath.substr(QDR.pluginRoot.length + 1)
+			if (org === '') {
+				org = lastLocation
+			}
 			if (curPath === QDR.pluginRoot && (!org || org.length===0 || org !== 'connect')) {
 				org = lastLocation
 			}
@@ -228,8 +236,9 @@ var QDR = (function(QDR) {
         $rootScope.$on('$routeChangeSuccess', function() {
             var path = $location.path();
 			if (path.startsWith(QDR.pluginRoot)) {
-				if (path != QDR.pluginRoot && path != QDR.pluginRoot + "/connect") {
-		            localStorage[QDR.LAST_LOCATION] = $location.path();
+				if (path !== QDR.pluginRoot && path !== (QDR.pluginRoot + "/") && path !== (QDR.pluginRoot + "/connect")) {
+		            localStorage[QDR.LAST_LOCATION] = path;
+QDR.log.info("saving page changed to " + path)
 				}
 			}
         });
@@ -237,40 +246,17 @@ var QDR = (function(QDR) {
 		$rootScope.$on( "$routeChangeStart", function(event, next, current) {
 			if (next && next.templateUrl == QDR.templatePath + "qdrConnect.html" && QDRService.connected) {
 				// clicked connect from another dispatch page
-				if (current && current.loadedTemplateUrl.startsWith(QDR.contextPath)) {
+				if (current && current.loadedTemplateUrl && current.loadedTemplateUrl.startsWith(QDR.contextPath)) {
 					return;
 				}
 				// clicked the Dispatch Router top level tab from a different plugin
-				var lastLocation = localStorage[QDR.LAST_LOCATION];
-				if (!angular.isDefined(lastLocation)) {
-					lastLocation = QDR.pluginRoot + "/overview";
-				}
+				var lastLocation = localStorage[QDR.LAST_LOCATION] || (QDR.pluginRoot + "/overview");
 				// show the last page visited
+QDR.log.info("showing dispatch tab: going to page " + lastLocation)
 				$location.path(lastLocation)
 			}
 	    });
-    /* Set up top-level link to our plugin.  Requires an object
-       with the following attributes:
 
-         id - the ID of this plugin, used by the perspective plugin
-              and by the preferences page
-         content - The text or HTML that should be shown in the tab
-         title - This will be the tab's tooltip
-         isValid - A function that returns whether or not this
-                   plugin has functionality that can be used for
-                   the current JVM.  The workspace object is passed
-                   in by hawtio's navbar controller which lets
-                   you inspect the JMX tree, however you can do
-                   any checking necessary and return a boolean
-         href - a function that returns a link, normally you'd
-                return a hash link like #/foo/bar but you can
-                also return a full URL to some other site
-         isActive - Called by hawtio's navbar to see if the current
-                    $location.url() matches up with this plugin.
-                    Here we use a helper from workspace that
-                    checks if $location.url() starts with our
-                    route.
-     */
     workspace.topLevelTabs.push({
       id: "dispatch",
       content: "Dispatch Router",

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/navbar.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/navbar.js b/console/hawtio/src/main/webapp/plugin/js/navbar.js
deleted file mode 100644
index 74cc5a6..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/navbar.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
-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) {
-
-  /**
-   * @property breadcrumbs
-   * @type {{content: string, title: string, isValid: isValid, href: string}[]}
-   *
-   * Data structure that defines the sub-level tabs for
-   * our plugin, used by the navbar controller to show
-   * or hide tabs based on some criteria
-   */
-  QDR.breadcrumbs = [
-    {
-        content: '<i class="icon-cogs"></i> Connect',
-        title: "Connect to a router",
-        isValid: function () { return true; },
-        href: "#/" + QDR.pluginName + "/connect"
-    },
-    {
-        content: '<i class="fa fa-home"></i> Overview',
-        title: "View router overview",
-        isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/" + QDR.pluginName + "/overview"
-      },
-    {
-        content: '<i class="icon-list "></i> Entities',
-        title: "View the attributes of the router entities",
-        isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/" + QDR.pluginName + "/list"
-      },
-    {
-        content: '<i class="icon-star-empty"></i> Topology',
-        title: "View router network topology",
-        isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/" + QDR.pluginName + "/topology"
-      },
-    {
-        content: '<i class="icon-align-left"></i> Schema',
-        title: "View dispatch schema",
-        isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/" + QDR.pluginName + "/schema",
-        right: true
-
-      }
-  ];
-  /**
-   * @function NavBarController
-   *
-   * @param $scope
-   * @param workspace
-   *
-   * The controller for this plugin's navigation bar
-   *
-   */
-QDR.NavBarController = function($scope, QDRService, QDRChartService, $location, $routeParams) {
-    $scope.breadcrumbs = QDR.breadcrumbs;
-    $scope.isValid = function(link) {
-      return link.isValid(QDRService, $location);
-    };
-
-    $scope.isActive = function(href) {
-		// highlight the connect tab if we are on the root page
-		if (($location.path() === QDR.pluginRoot) && (href.split("#")[1] === QDR.pluginRoot + "/connect"))
-			return true
-        return href.split("#")[1] == $location.path();
-    };
-
-    $scope.isRight = function (link) {
-        return angular.isDefined(link.right);
-    };
-
-    $scope.hasChart = function (link) {
-        if (link.href == "#/" + QDR.pluginName + "/charts") {
-            return QDRChartService.charts.some(function (c) { return c.dashboard });
-        }
-    }
-
-	$scope.isDashboardable = function () {
-		return  ($location.path().indexOf("schema") < 0 && $location.path().indexOf("connect") < 0);
-	}
-
-	$scope.addToDashboardLink = function () {
-		var href = "#" + $location.path();
-		var size = angular.toJson({
-                size_x: 2,
-                size_y: 2
-              });
-
-        var routeParams = angular.toJson($routeParams);
-        var title = "Dispatch Router";
-	    return "/hawtio/#/dashboard/add?tab=dashboard" +
-	          "&href=" + encodeURIComponent(href) +
-	          "&routeParams=" + encodeURIComponent(routeParams) +
-	          "&title=" + encodeURIComponent(title) +
-	          "&size=" + encodeURIComponent(size);
-    };
-
-  };
-
-
-  return QDR;
-
-} (QDR || {}));
diff --git a/console/hawtio/src/main/webapp/plugin/js/navbar.js b/console/hawtio/src/main/webapp/plugin/js/navbar.js
new file mode 120000
index 0000000..e88b618
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/navbar.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/navbar.js
\ 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


[03/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrOverview.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverview.js b/console/stand-alone/plugin/js/qdrOverview.js
index d787a5e..d16b8e2 100644
--- a/console/stand-alone/plugin/js/qdrOverview.js
+++ b/console/stand-alone/plugin/js/qdrOverview.js
@@ -34,99 +34,118 @@ var QDR = (function (QDR) {
    *
    * Controller that handles the QDR overview page
    */
-	QDR.module.controller("QDR.OverviewController", ['$scope', 'uiGridConstants', 'QDRService', function($scope, uiGridConstants, QDRService) {
+	QDR.module.controller("QDR.OverviewController", ['$scope', 'QDRService', '$location', '$timeout', function($scope, QDRService, $location, $timeout) {
 
+		console.log("QDR.OverviewControll started with location of " + $location.path() + " and connection of  " + QDRService.connected);
+		var columnStateKey = 'QDRColumnKey.';
+		var OverviewExpandedKey = "QDROverviewExpanded"
+		var OverviewActivatedKey = "QDROverviewActivated"
+		var treeRoot;   // the dynatree root node. initialized once log data is received
 
-		if (!angular.isDefined(QDRService.schema))
-		    return;
-
-		var nodeIds = QDRService.nodeIdList();
-		var currentTimer;
+		// 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;
+		}
+		$scope.linkFields = []
+		$scope.link = null;
 		var refreshInterval = 5000
-		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;
-        })();
 	    $scope.modes = [
 	    	{title: 'Overview', name: 'Overview', right: false}
 	    	];
 
+		$scope.tmplOverviewTree = QDR.templatePath + 'tmplOverviewTree.html';
 		$scope.templates =
-		    [ { name: 'Routers', url: 'routers.html'},
+		[     { name: 'Routers', url: 'routers.html'},
 		      { name: 'Router', url: 'router.html'},
               { name: 'Addresses', url: 'addresses.html'},
 		      { name: 'Address', url: 'address.html'},
+              { name: 'Links', url: 'links.html'},
+		      { name: 'Link', url: 'link.html'},
               { name: 'Connections', url: 'connections.html'},
 		      { name: 'Connection', url: 'connection.html'},
               { name: 'Logs', url: 'logs.html'},
-              { name: 'Log', url: 'log.html'} ];
+              { name: 'Log', url: 'log.html'}
+        ];
+		var topLevelChildren = [];
 
-		$scope.getGridHeight = function (data) {
-	       // add 1 for the header row
-	       return {height: (($scope[data.data].length + 1) * 30) + "px"};
+		$scope.allRouterSelected = function (row ) {
+			console.log("row selected" + row)
 		}
-		$scope.overview = new Folder("Overview");
+		function afterSelectionChange(rowItem, checkAll) {
+			var nodeId = rowItem.entity.nodeId;
+			$("#overtree").dynatree("getTree").activateKey(nodeId);
+        }
 
 		$scope.allRouterFields = [];
-		var allRouterCols = [
-			 {
-				 field: 'routerId',
-				 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'
-             }
-		];
+		$scope.allRouterSelections = [];
 		$scope.allRouters = {
+		    saveKey: 'allRouters',
 			data: 'allRouterFields',
-			columnDefs: allRouterCols,
-			enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-            enableVerticalScrollbar: 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'
+                 }
+            ],
 			enableColumnResize: true,
 			multiSelect: false,
-			enableRowHeaderSelection: false,
-			noUnselect: true,
-			enableSelectAll: false,
-			enableRowSelection: true,
-			onRegisterApi: function (gridApi) {
-				gridApi.selection.on.rowSelectionChanged($scope, function(row) {
-					if (row.isSelected) {
-						var nodeId = row.entity.nodeId;
-						$("#overtree").fancytree("getTree").activateKey(nodeId);
-					}
-				});
-		    }
+			selectedItems: $scope.allRouterSelections,
+			afterSelectionChange: function(data) {
+				if (data.selected) {
+					var selItem = $scope.allRouterSelections[0]
+					var nodeId = selItem.nodeId
+					// activate Routers->nodeId in the tree
+					$("#overtree").dynatree("getTree").activateKey(nodeId);
+
+				}
+            }
 		};
+
 		// get info for all routers
 		var allRouterInfo = function () {
-			nodeIds = QDRService.nodeIdList()
+			var nodeIds = QDRService.nodeIdList()
 			var expected = Object.keys(nodeIds).length
 			var received = 0;
 			var allRouterFields = [];
@@ -145,63 +164,59 @@ var QDR = (function (QDR) {
 				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, response) {
-						var results = response.aggregates
-						results.forEach ( function (result) {
-
-							var routerId = QDRService.valFor(response.attributeNames, result, "routerId").sum
+					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], "routerId")
 							allRouterFields.some( function (connField) {
 								if (routerId === connField.routerId) {
-									response.attributeNames.forEach ( function (attrName) {
-										connField[attrName] = QDRService.valFor(response.attributeNames, result, attrName).sum
+									result.attributeNames.forEach ( function (attrName) {
+										connField[attrName] = QDRService.valFor(result.attributeNames, result.results[0], attrName)
 									})
 									return true
 								}
 								return false
 							})
-						})
-						$scope.allRouterFields = allRouterFields
-						$scope.$apply()
-						if (currentTimer) {
-							clearTimeout(currentTimer)
 						}
-						currentTimer = setTimeout(allRouterInfo, refreshInterval);
-					}, nodeIds[0])
+						$scope.allRouterFields = allRouterFields
+					}, nodeIds[0], false)
 				}
 			}
-			nodeIds.forEach ( function (nodeId) {
+			nodeIds.forEach ( function (nodeId, i) {
 				QDRService.getNodeInfo(nodeId, ".connection", ["role"], gotNodeInfo)
 			})
-
+			loadColState($scope.allRouters)
+			updateRouterTree(nodeIds)
 		}
 
+		$scope.routerFields = []
+        $scope.routerGrid = {
+            saveKey: 'routerGrid',
+            data: 'routerFields',
+            columnDefs: [
+                 {
+                     field: 'attribute',
+                     displayName: 'Attribute',
+                     saveKey: 'routerGrid',
+                     width: '40%'
+                 },
+                 {
+                     field: 'value',
+                     displayName: 'Value',
+                     width: '40%'
+                 }
+            ],
+            enableColumnResize: true,
+            multiSelect: false
+        }
+
 		// get info for a single router
 		var routerInfo = function (node) {
 			$scope.router = node
-			$scope.routerFields = []
-			var cols = [
-				 {
-					 field: 'attribute',
-					 displayName: 'Attribute',
-					 width: '40%'
-				 },
-				 {
-					 field: 'value',
-					 displayName: 'Value',
-					 width: '40%'
-				 }
-			]
-			$scope.routerGrid = {
-				data: 'routerFields',
-				columnDefs: cols,
-				enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-	            enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-				enableColumnResize: true,
-				multiSelect: false
-			}
 
+			$scope.routerFields = [];
 			$scope.allRouterFields.some( function (field) {
-				if (field.routerId === node.title) {
+				if (field.routerId === node.data.title) {
 					Object.keys(field).forEach ( function (key) {
 						if (key !== '$$hashKey')
 							$scope.routerFields.push({attribute: key, value: field[key]})
@@ -209,90 +224,90 @@ var QDR = (function (QDR) {
 					return true
 				}
 			})
-
-			$scope.$apply()
-			if (currentTimer) {
-				clearTimeout(currentTimer)
-				currentTimer = null
-			}
+	        loadColState($scope.routerGrid);
 		}
 
-		// get info for a all addresses
-		var allAddressInfo = function () {
-			$scope.addressFields = []
-			var addressCols = [
-				 {
-					 field: 'address',
-					 displayName: 'address'
-				 },
-				 {
-					 field: 'class',
-					 displayName: 'class'
-				 },
-				 {
-					 field: 'phase',
-					 displayName: 'phase',
-					 cellClass: 'grid-align-value'
-				 },
-				 {
-					 field: 'inproc',
-					 displayName: 'in-proc'
-				 },
-				 {
-					 field: 'local',
-					 displayName: 'local',
-					 cellClass: 'grid-align-value'
-				 },
-				 {
-					 field: 'remote',
-					 displayName: 'remote',
-					 cellClass: 'grid-align-value'
-				 },
-				 {
-					 field: 'in',
-					 displayName: 'in',
-					 cellClass: 'grid-align-value'
-				 },
-				 {
-					 field: 'out',
-					 displayName: 'out',
-					 cellClass: 'grid-align-value'
-				 }
-			]
-			$scope.selectedAddresses = []
-			$scope.addressGrid = {
-				data: 'addressFields',
-				columnDefs: addressCols,
-				enableColumnResize: true,
-				enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-	            enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-				multiSelect: false,
-				enableRowHeaderSelection: false,
-				noUnselect: true,
-				enableSelectAll: false,
-				enableRowSelection: true,
-				onRegisterApi: function (gridApi) {
-					gridApi.selection.on.rowSelectionChanged($scope, function(row) {
-						if (row.isSelected) {
-							var key = row.entity.uid;
-							$("#overtree").fancytree("getTree").activateKey(key);
-						}
-					});
-			    }
-			}
+		$scope.addressesData = []
+		$scope.selectedAddresses = []
+		$scope.addressesGrid = {
+			saveKey: 'addressesGrid',
+			data: 'addressesData',
+			columnDefs: [
+                 {
+                     field: 'address',
+                    saveKey: 'addressesGrid',
+                     displayName: 'address'
+                 },
+                 {
+                     field: 'class',
+                     displayName: 'class'
+                 },
+                 {
+                     field: 'phase',
+                     displayName: 'phase',
+                     cellClass: 'grid-align-value'
+                 },
+                 {
+                     field: 'inproc',
+                     displayName: 'in-proc'
+                 },
+                 {
+                     field: 'local',
+                     displayName: 'local',
+                     cellClass: 'grid-align-value'
+                 },
+                 {
+                     field: 'remote',
+                     displayName: 'remote',
+                     cellClass: 'grid-align-value'
+                 },
+                 {
+                     field: 'in',
+                     displayName: 'in',
+                     cellClass: 'grid-align-value'
+                 },
+                 {
+                     field: 'out',
+                     displayName: 'out',
+                     cellClass: 'grid-align-value'
+                 }
+            ],
+			enableColumnResize: true,
+			multiSelect: false,
+			selectedItems: $scope.selectedAddresses,
+			afterSelectionChange: function(data) {
+				if (data.selected) {
+					var selItem = data.entity;
+					var nodeId = selItem.uid
+					$("#overtree").dynatree("getTree").activateKey(nodeId);
+				}
+            }
+		};
 
+		// get info for all addresses
+		var allAddressInfo = function () {
 			var gotAllAddressFields = function ( addressFields ) {
-				$scope.addressFields = 	addressFields
-				$scope.$apply()
-				if (currentTimer) {
-					clearTimeout(currentTimer)
+				// 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
 				}
-				currentTimer = setTimeout(allAddressInfo, refreshInterval);
+				$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"
@@ -331,7 +346,6 @@ var QDR = (function (QDR) {
 			}
 
 			var addressFields = []
-			nodeIds = QDRService.nodeIdList()
 			QDRService.getMultipleNodeInfo(nodeIds, "router.address", [], function (nodeIds, entity, response) {
 				response.aggregates.forEach( function (result) {
 					var prettySum = function (field) {
@@ -365,139 +379,667 @@ var QDR = (function (QDR) {
 			}, nodeIds[0])
 		}
 
+		var updateLinkGrid = function ( linkFields ) {
+			$scope.linkFields = linkFields
+			// if we have a selected link
+			if ($scope.link) {
+				// find the selected link in the array of all links
+				var links = $scope.linkFields.filter(function (link) {
+					return link.name === $scope.link.data.fields.name;
+				})
+				if (links.length > 0) {
+					// linkInfo() is the function that is called by dynatree when a link is selected
+					// It is passed a dynatree node. We need to simulate that node type to update the link grid
+					linkInfo({data: {title: links[0].title, fields: links[0]}})
+				}
+			}
+		}
+
+		// get info for a all links
+		$scope.selectedLinks = []
+		$scope.linksGrid = {
+			saveKey: 'linksGrid',
+			data: 'linkFields',
+			columnDefs: [
+                 {
+                     field: 'link',
+                     displayName: 'Link',
+                     groupable:	false,
+                     saveKey: 'linksGrid',
+                     width: '12%'
+                 },
+                 {
+                     field: 'linkType',
+                     displayName: 'Link type',
+                     groupable:	false,
+                     width: '10%'
+                 },
+                 {
+                     field: 'linkDir',
+                     displayName: 'Link dir',
+                     groupable:	false,
+                     width: '8%'
+                 },
+                 {
+                     field: 'adminStatus',
+                     displayName: 'Admin status',
+                     groupable:	false,
+                     width: '10%'
+                 },
+                 {
+                     field: 'operStatus',
+                     displayName: 'Oper status',
+                     groupable:	false,
+                     width: '10%'
+                 },
+                 {
+                     field: 'deliveryCount',
+                     displayName: 'Delivery Count',
+                     groupable:	false,
+                     cellClass: 'grid-align-value',
+                     width: '12%'
+                 },
+                 {
+                     field: 'rate',
+                     displayName: 'Rate',
+                     groupable:	false,
+                     cellClass: 'grid-align-value',
+                     width: '8%'
+                 },
+                 {
+                     field: 'uncounts',
+                     displayName: 'Outstanding',
+                     groupable:	false,
+                     cellClass: 'grid-align-value',
+                     width: '10%'
+                 },
+                 {
+                     field: 'owningAddr',
+                     displayName: 'Address',
+                     groupable:	false,
+                     width: '20%'
+                 }
+            ],
+			enableColumnResize: true,
+			enableColumnReordering: true,
+			showColumnMenu: true,
+			rowTemplate: 'linkRowTemplate.html',
+			// aggregateTemplate: "linkAggTemplate.html",
+	        multiSelect: false,
+			selectedItems: $scope.selectedLinks,
+			afterSelectionChange: function(data) {
+				if (data.selected) {
+					var selItem = data.entity;
+					var nodeId = selItem.uid
+					$("#overtree").dynatree("getTree").activateKey(nodeId);
+				}
+            }
+		};
 
+
+		$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) {
+                    grid.columnDefs[index].width = col[0];
+                    grid.columnDefs[index].visible = col[1]
+                })
+            }
+        }
+		var allLinkInfo = function () {
+			getAllLinkFields([updateLinkGrid, updateLinkTree])
+	        loadColState($scope.linksGrid);
+		}
+
+		var getAllLinkFields = function (callbacks) {
+			var nodeIds = QDRService.nodeIdList()
+			var linkFields = []
+			var now = Date.now()
+			var rate = function (response, result) {
+				var name = QDRService.valFor(response.attributeNames, result, "linkName").sum
+				var oldname = $scope.linkFields.filter(function (link) {
+					return link.linkName === name
+				})
+				if (oldname.length > 0) {
+					var elapsed = (now - oldname[0].timestamp) / 1000;
+					var delivered = QDRService.valFor(response.attributeNames, result, "deliveryCount").sum - 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
+				}
+			}
+			QDRService.getMultipleNodeInfo(nodeIds, "router.link", [], 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 uncounts = function () {
+						var und = QDRService.valFor(response.attributeNames, result, "undeliveredCount").sum
+						var uns = QDRService.valFor(response.attributeNames, result, "unsettledCount").sum
+						return und + uns
+					}
+					var nameIndex = response.attributeNames.indexOf('name')
+					var linkName = function () {
+						var details = result[nameIndex].detail
+						var names = []
+						details.forEach( function (detail) {
+							if (detail.node.endsWith(':'))
+								names.push(detail.node.slice(0, -1))
+							else
+								names.push(detail.node)
+						})
+						//var namestr = names.join('-')
+						var namestr = names.length > 0 ? names[0] : ""
+						return namestr + ':' + QDRService.valFor(response.attributeNames, result, "identity").sum
+					}
+					var fixAddress = function () {
+						var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr").sum || ""
+						/*
+						     - "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]
+								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 + ")"
+									}
+								}
+							}
+						}
+						return address || owningAddr;
+					}
+
+					var adminStatus = QDRService.valFor(response.attributeNames, result, "adminStatus").sum
+					var operStatus = QDRService.valFor(response.attributeNames, result, "operStatus").sum
+					var linkName = linkName()
+					var linkType = QDRService.valFor(response.attributeNames, result, "linkType").sum
+					if ($scope.currentLinkFilter === "" || $scope.currentLinkFilter === linkType) {
+						linkFields.push({
+							link:       linkName,
+							title:      linkName,
+							uncounts:   uncounts(),
+							operStatus: operStatus,
+							adminStatus:adminStatus,
+							owningAddr: fixAddress(),
+							deliveryCount:prettySum("deliveryCount") + " ",
+							rawDeliveryCount: QDRService.valFor(response.attributeNames, result, "deliveryCount").sum,
+							name: QDRService.valFor(response.attributeNames, result, "name").sum,
+							linkName: QDRService.valFor(response.attributeNames, result, "linkName").sum,
+							capacity: QDRService.valFor(response.attributeNames, result, "capacity").sum,
+							connectionId: QDRService.valFor(response.attributeNames, result, "connectionId").sum,
+							linkDir: QDRService.valFor(response.attributeNames, result, "linkDir").sum,
+							linkType: linkType,
+							peer: QDRService.valFor(response.attributeNames, result, "peer").sum,
+							type: QDRService.valFor(response.attributeNames, result, "type").sum,
+							undeliveredCount: QDRService.valFor(response.attributeNames, result, "undeliveredCount").sum,
+							unsettledCount: QDRService.valFor(response.attributeNames, result, "unsettledCount").sum,
+							uid:     linkName,
+							timestamp: now,
+							rate: rate(response, result)
+						})
+					}
+				})
+				callbacks.forEach( function (cb) {
+					cb(linkFields)
+				})
+			}, nodeIds[0])
+		}
+
+		$scope.allConnectionFields = []
+		$scope.allConnectionSelections = [];
+		$scope.allConnectionGrid = {
+			saveKey: 'allConnGrid',
+            data: 'allConnectionFields',
+            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'
+                 }
+            ],
+            enableColumnResize: true,
+            multiSelect: false,
+            selectedItems: $scope.allConnectionSelections,
+            afterSelectionChange: function(data) {
+                if (data.selected) {
+                    var selItem = $scope.allConnectionSelections[0]
+                    var nodeId = selItem.host
+                    // activate Routers->nodeId in the tree
+                    $("#overtree").dynatree("getTree").activateKey(nodeId);
+                }
+            }
+        };
 		// get info for a all connections
 		var allConnectionInfo = function () {
-			$scope.allConnectionFields = []
-			var allConnectionCols = [
-				 {
-					 field: 'host',
-					 displayName: 'host'
-				 },
-				 {
-					 field: 'container',
-					 displayName: 'container'
-				 },
-				 {
-					 field: 'role',
-					 displayName: 'role'
-				 },
-				 {
-					 field: 'dir',
-					 displayName: 'dir'
-				 },
-				 {
-					 field: 'security',
-					 displayName: 'security'
-				 },
-				 {
-					 field: 'authentication',
-					 displayName: 'authentication'
-				 }
-			]
-			$scope.allConnectionGrid = {
-				data: 'allConnectionFields',
-				columnDefs: allConnectionCols,
-				enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-	            enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-				enableColumnResize: true,
-				multiSelect: false,
-				enableRowHeaderSelection: false,
-				noUnselect: true,
-				enableSelectAll: false,
-				enableRowSelection: true,
-				onRegisterApi: function (gridApi) {
-					gridApi.selection.on.rowSelectionChanged($scope, function(row) {
-						if (row.isSelected) {
-							var host = row.entity.host;
-							$("#overtree").fancytree("getTree").activateKey(host);
-						}
-					});
-			    }
-			}
-			connections.children.forEach( function (connection) {
-				$scope.allConnectionFields.push(connection.fields)
+			var nodeIds = QDRService.nodeIdList()
+			updateConnectionTree(nodeIds)
+			$scope.allConnectionFields = [];;
+            var connectionNodes = $("#overtree").dynatree("getTree").getNodeByKey('Connections').getChildren()
+			connectionNodes.forEach( function (connection) {
+				$scope.allConnectionFields.push(connection.data.fields)
 			})
-			$scope.$apply()
-			if (currentTimer) {
-				clearTimeout(currentTimer)
-				currentTimer = null
-			}
+	        loadColState($scope.allConnectionGrid);
+		}
+
+		$scope.addressFields = []
+		$scope.addressGrid = {
+			saveKey: 'addGrid',
+			data: 'addressFields',
+			columnDefs: [
+                 {
+                     field: 'attribute',
+                     displayName: 'Attribute',
+                     saveKey: 'addGrid',
+                     width: '40%'
+                 },
+                 {
+                     field: 'value',
+                     displayName: 'Value',
+                     width: '40%'
+                 }
+            ],
+			enableColumnResize: true,
+			multiSelect: false
 		}
 
 		// get info for a single address
 		var addressInfo = function (address) {
+			if (!address)
+				return;
 			$scope.address = address
-			$scope.addressFields = []
-			var cols = [
-				 {
-					 field: 'attribute',
-					 displayName: 'Attribute',
-					 width: '40%'
-				 },
-				 {
-					 field: 'value',
-					 displayName: 'Value',
-					 width: '40%'
-				 }
-			]
-			$scope.addressGrid = {
-				data: 'addressFields',
-				columnDefs: cols,
-				enableColumnResize: true,
-				enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-	            enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-				multiSelect: false
-			}
+			$scope.addressFields = [];
 
 			var fields = Object.keys(address.data.fields)
 			fields.forEach( function (field) {
 				if (field != "title" && field != "uid")
 					$scope.addressFields.push({attribute: field, value: address.data.fields[field]})
 			})
+	        loadColState($scope.addressGrid);
+		}
 
-			$scope.$apply()
-			if (currentTimer) {
-				clearTimeout(currentTimer)
-				currentTimer = null
-			}
+		$scope.singleLinkFields = []
+		$scope.linkGrid = {
+		    saveKey: 'linkGrid',
+			data: 'singleLinkFields',
+			columnDefs: [
+                 {
+                    field: 'attribute',
+                    displayName: 'Attribute',
+                     width: '40%'
+                 },
+                 {
+                    field: 'value',
+                    displayName: 'Value',
+                    width: '40%'
+                 }
+            ],
+			enableColumnResize: true,
+			multiSelect: false
+		}
+
+		// 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.data.fields)
+			var excludeFields = ["title", "uid", "uncounts", "rawDeliveryCount", "timestamp"]
+			fields.forEach( function (field) {
+				if (excludeFields.indexOf(field) == -1)
+					$scope.singleLinkFields.push({attribute: field, value: link.data.fields[field]})
+			})
+	        loadColState($scope.linkGrid);
 		}
 
 		// get info for a single connection
+		$scope.connectionModes = [{
+	        content: '<a><i class="icon-list"></i> Attriutes</a>',
+			id: 'attributes',
+			title: "View connection attributes",
+	        isValid: function () { return true; }
+	    },
+	    {
+	        content: '<a><i class="icon-link"></i> Links</a>',
+	        id: 'links',
+	        title: "Show links",
+	        isValid: function () { return true }
+	    }
+	    ];
+        $scope.currentMode = $scope.connectionModes[0];
+		$scope.isModeSelected = function (mode) {
+			return mode === $scope.currentMode;
+		}
+		$scope.connectionLinks = [];
+		var updateConnectionLinks = function () {
+			var n = $scope.connection.data.fields
+			var key = n.routerId
+			var nodeInfo = QDRService.topology.nodeInfo();
+			var links = nodeInfo[key]['.router.link'];
+			var linkTypeIndex = links.attributeNames.indexOf('linkType');
+			var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+			$scope.connectionLinks = [];
+			links.results.forEach( function (link) {
+				if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.identity) {
+					var l = {};
+					l.owningAddr = QDRService.valFor(links.attributeNames, link, 'owningAddr');
+					l.dir = QDRService.valFor(links.attributeNames, link, 'linkDir');
+					if (l.owningAddr && l.owningAddr.length > 2)
+						if (l.owningAddr[0] === 'M')
+							l.owningAddr = l.owningAddr.substr(2)
+						else
+							l.owningAddr = l.owningAddr.substr(1)
+
+					l.adminStatus = QDRService.valFor(links.attributeNames, link, 'adminStatus');
+					l.operStatus = QDRService.valFor(links.attributeNames, link, 'operStatus');
+					l.identity = QDRService.valFor(links.attributeNames, link, 'identity')
+					l.connectionId = QDRService.valFor(links.attributeNames, link, 'connectionId')
+/*
+						l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
+						l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
+						l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
+*/
+					// ---------------------------------------------
+					// TODO: remove this fake quiescing/reviving logic when the routers do the work
+					if ($scope.quiesceState.linkStates[l.identity])
+						l.adminStatus = $scope.quiesceState.linkStates[l.identity];
+					if ($scope.quiesceState.operStates[l.identity])
+						l.operStatus = $scope.quiesceState.operStates[l.identity];
+					if ($scope.quiesceState.state == 'quiescing') {
+						if (l.adminStatus === 'enabled') {
+							var chance = Math.floor(Math.random() * 2);
+							if (chance == 1) {
+								l.adminStatus = 'disabled';
+								$scope.quiesceState.linkStates[l.identity] = 'disabled';
+								$scope.quiesceState.operStates[l.identity] = 'idle';
+							}
+						}
+					}
+					if ($scope.quiesceState.state == 'reviving') {
+						if (l.adminStatus === 'disabled') {
+							var chance = Math.floor(Math.random() * 2);
+							if (chance == 1) {
+								l.adminStatus = 'enabled';
+								$scope.quiesceState.linkStates[l.identity] = 'enabled';
+								$scope.quiesceState.operStates[l.identity] = 'up';
+							}
+						}
+					}
+					if ($scope.quiesceState.linkStates[l.identity] === 'disabled') {
+						l.unsettledCount = 0;
+						l.undeliveredCount = 0;
+						l.operState = 'idle'
+						$scope.quiesceState.operStates[l.identity] = l.operState
+					} else {
+						l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
+						l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
+						l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
+						l.operState = 'up'
+						$scope.quiesceState.operStates[l.identity] = l.operState
+					}
+					// ---------------------------------------------
+
+					$scope.connectionLinks.push(l)
+				}
+			})
+			$scope.connectionLinksGrid.updateState()
+		}
+
+		$scope.selectMode = function (mode) {
+			$scope.currentMode = mode;
+			if (mode.id === 'links') {
+		        QDRService.addUpdatedAction("connectionLinks", updateConnectionLinks)
+				updateConnectionLinks();
+			} else {
+				QDRService.delUpdatedAction("connectionLinks");
+			}
+		}
+		$scope.isValid = function (mode) {
+			return mode.isValid()
+		}
+		$scope.quiesceState = {
+			state: 'enabled',
+			buttonText: 'Quiesce',
+			buttonDisabled: false,
+			linkStates: {},
+			operStates: {}
+		}
+		$scope.quiesceAllClicked = function () {
+			var state = $scope.quiesceState.state;
+			if (state === 'enabled') {
+				// start quiescing all links
+				$scope.quiesceState.state = 'quiescing';
+			} else if (state === 'quiesced') {
+				// start reviving all links
+				$scope.quiesceState.state = 'reviving';
+			}
+			$scope.connectionLinksGrid.updateState();
+		}
+		$scope.quiesceClass = function (row) {
+			var stateClassMap = {
+				enabled: 'btn-primary',
+				quiescing: 'btn-warning',
+				reviving: 'btn-warning',
+				quiesced: 'btn-danger'
+			}
+			return stateClassMap[$scope.quiesceState.state];
+		}
+		$scope.quiesceLinkClass = function (row) {
+			var stateClassMap = {
+				enabled: 'btn-primary',
+				disabled: 'btn-danger'
+			}
+			return stateClassMap[row.entity.adminStatus]
+		}
+		$scope.quiesceLink = function (row) {
+			var state = row.entity.adminStatus === 'enabled' ? 'disabled' : 'enabled';
+			var operState = state === 'enabled' ? 'up' : 'idle';
+			$scope.quiesceState.linkStates[row.entity.identity] = state;
+			$scope.quiesceState.operStates[row.entity.identity] = operState;
+		}
+		$scope.quiesceLinkDisabled = function (row) {
+			return false;
+		}
+		$scope.quiesceLinkText = function (row) {
+			return row.entity.adminStatus === 'disabled' ? "Revive" : "Quiesce";
+		}
+		$scope.quiesceHide = function () {
+			return $scope.connectionLinks.length == 0;
+		}
+		$scope.connectionLinksGrid = {
+		    saveKey: 'connLinksGrid',
+
+			data: 'connectionLinks',
+			updateState: function () {
+				var state = $scope.quiesceState.state;
+
+				// count enabled and disabled links for this connection
+				var enabled = 0, disabled = 0;
+				$scope.connectionLinks.forEach ( function (link) {
+					if (link.adminStatus === 'enabled')
+						++enabled;
+					if (link.adminStatus === 'disabled')
+						++disabled;
+				})
+
+				var linkCount = $scope.connectionLinks.length;
+				if (linkCount == 0) {
+					$scope.quiesceState.buttonText = 'Quiesce';
+					$scope.quiesceState.buttonDisabled = false;
+					$scope.quiesceState.state = 'enabled'
+					return;
+				}
+				// if state is quiescing and any links are enabled, button should say 'Quiescing' and be disabled
+				if (state === 'quiescing' && (enabled > 0)) {
+					$scope.quiesceState.buttonText = 'Quiescing';
+					$scope.quiesceState.buttonDisabled = true;
+				} else
+				// if state is enabled and all links are disabled, button should say Revive and be enabled. set state to quisced
+				// if state is quiescing and all links are disabled, button should say 'Revive' and be enabled. set state to quiesced
+				if ((state === 'quiescing' || state === 'enabled') && (disabled === linkCount)) {
+					$scope.quiesceState.buttonText = 'Revive';
+					$scope.quiesceState.buttonDisabled = false;
+					$scope.quiesceState.state = 'quiesced'
+				} else
+				// if state is reviving and any links are disabled, button should say 'Reviving' and be disabled
+				if (state === 'reviving' && (disabled > 0)) {
+					$scope.quiesceState.buttonText = 'Reviving';
+					$scope.quiesceState.buttonDisabled = true;
+				} else
+				// if state is reviving or quiesced and all links are enabled, button should say 'Quiesce' and be enabled. set state to enabled
+				if ((state === 'reviving' || state === 'quiesced') && (enabled === linkCount)) {
+					$scope.quiesceState.buttonText = 'Quiesce';
+					$scope.quiesceState.buttonDisabled = false;
+					$scope.quiesceState.state = 'enabled'
+				}
+
+				if ($scope.quiesceState.state === 'quiesced') {
+					d3.selectAll('.external.connection.dynatree-active')
+						.classed('quiesced', true)
+				} else {
+					d3.selectAll('.external.connection.dynatree-active.quiesced')
+						.classed('quiesced', false)
+				}
+			},
+            columnDefs: [
+			{
+				field: 'adminStatus',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+			    saveKey: 'connLinksGrid',
+				displayName: 'Admin state'
+			},
+			{
+				field: 'operStatus',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				displayName: 'Oper state'
+			},
+			{
+				field: 'dir',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				displayName: 'dir'
+			},
+			{
+				field: 'owningAddr',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				displayName: 'Address'
+			},
+			{
+				field: 'deliveryCount',
+				displayName: 'Delivered',
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				cellClass: 'grid-values'
+
+			},
+			{
+				field: 'undeliveredCount',
+				displayName: 'Undelivered',
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				cellClass: 'grid-values'
+			},
+			{
+				field: 'unsettledCount',
+				displayName: 'Unsettled',
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				cellClass: 'grid-values'
+			}/*,
+			{
+				cellClass: 'gridCellButton',
+				cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>'
+			}*/
+			]
+		}
+
+		$scope.connectionFields = []
+		$scope.connectionGrid = {
+		    saveKey: 'connGrid',
+			data: 'connectionFields',
+			columnDefs: [
+                 {
+                     field: 'attribute',
+                     displayName: 'Attribute',
+                    saveKey: 'connGrid',
+                     width: '40%'
+                 },
+                 {
+                     field: 'value',
+                     displayName: 'Value',
+                     width: '40%'
+                 }
+            ],
+			enableColumnResize: true,
+			multiSelect: false
+		}
+
 		var connectionInfo = function (connection) {
 			$scope.connection = connection
-			$scope.connectionFields = []
-			var cols = [
-				 {
-					 field: 'attribute',
-					 displayName: 'Attribute',
-					 width: '40%'
-				 },
-				 {
-					 field: 'value',
-					 displayName: 'Value',
-					 width: '40%'
-				 }
-			]
-			$scope.connectionGrid = {
-				data: 'connectionFields',
-				columnDefs: cols,
-				enableColumnResize: true,
-				enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-	            enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-				multiSelect: false
-			}
 
+			$scope.connectionFields = [];
 			var fields = Object.keys(connection.data.fields)
 			fields.forEach( function (field) {
 				$scope.connectionFields.push({attribute: field, value: connection.data.fields[field]})
 			})
-
-			$scope.$apply()
-			if (currentTimer) {
-				clearTimeout(currentTimer)
-				currentTimer = null
-			}
+			$scope.selectMode($scope.currentMode)
+	        loadColState($scope.connectionGrid);
 		}
 
 		// get info for a all logs
@@ -509,142 +1051,309 @@ var QDR = (function (QDR) {
 			$scope.log = node
 		}
 
-		var activated = function (event, node) {
+		var getExpandedList = function () {
+			if (!treeRoot)
+				return;
+			var list = [];
+			if (treeRoot.visit) {
+				treeRoot.visit(function(node){
+	                if (node.isExpanded()) {
+	                    list.push(node.data.parent)
+	                }
+	            });
+			}
+			return list;
+		}
+
+		var updateExpanded = function () {
+			if (!treeRoot)
+				return;
+			if (treeRoot.visit) {
+				treeRoot.visit(function(node){
+	                if (node.isExpanded() || node.isActive()) {
+	                    node.data.info(node)
+	                }
+	            });
+			}
+		}
+
+		// 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)
+		}
+
+		// activated is called each time a tree node is clicked
+		// based on which node is clicked, load the correct data grid template and start getting the data
+		var activated = function (node) {
 			//QDR.log.debug("node activated: " + node.data.title)
-			node = node.node;
 			var type = node.data.type;
+			saveExpanded()
+			saveActivated(node.data.key)
+
 			var template = $scope.templates.filter( function (tpl) {
 				return tpl.name == type;
 			})
 			$scope.template = template[0];
-			// Call the function associated with this type passing in the node that was selected
-			// In dynatree I could save the function to call in the node, but not in FancyTree
-			if (treeTypeMap[type])
-				treeTypeMap[type](node);
-
-			/*if (node.data.info)
-				node.data.info(node)
-			*/
+			// the nodes info function will fetch the grids data
+			if (node.data.info) {
+				$timeout(function () {node.data.info(node)})
+			}
+
+		}
+        $scope.template = {url: ''};
+
+		if (!QDRService.connected) {
+			QDRService.redirectWhenConnected("overview")
+			return;
+		}
+
+		// we are currently connected. setup a handler to get notified if we are ever disconnected
+		QDRService.addDisconnectAction( function () {
+			QDRService.redirectWhenConnected("overview")
 			$scope.$apply();
+		})
+
+	/* --------------------------------------------------
+	 *
+     * setup the tree on the left
+     *
+     * -------------------------------------------------
+     */
+		// utility function called by each top level tree node when it needs to populate its child nodes
+		var updateLeaves = function (leaves, parentKey, parentFolder, worker) {
+			var scrollTree = $('.qdr-overview.pane.left .pane-viewport')
+			var scrollTop = scrollTree.scrollTop();
+			var tree;
+			try {
+				tree = $("#overtree").dynatree("getTree")
+			} catch (e) {
+				// tree is not initialized yet
+				tree = {}
+			}
+			var parentNode = tree.count ? tree.getNodeByKey(parentKey) : null;
+			// if we were called after the tree is created, replace the existing nodes
+			var activeNode;
+			if (parentNode) {
+				activeNode = tree.getActiveNode();
+				parentNode.removeChildren();
+			}
+			leaves.forEach( function (leaf) {
+				var childFolder = worker(leaf)
+				if (parentNode)
+					parentNode.addChild(childFolder)
+				else
+					parentFolder.children.push(childFolder)
+			})
+			scrollTree.scrollTop(scrollTop)
+			if (activeNode) {
+				var newNode = tree.getNodeByKey(activeNode.data.key)
+				newNode.activateSilently()
+			}
 		}
-        $scope.template = $scope.templates[0];
 
+		// get saved tree state
+        var lastKey = loadActivatedNode();
+		var expandedNodeList = loadExpandedNodeList();
+
+		// create a routers tree branch
 		var routers = new Folder("Routers")
 		routers.type = "Routers"
-		//routers.info = allRouterInfo
-		routers.focus = true
-		routers.expanded = true
+		routers.info = allRouterInfo
+		routers.activate = lastKey === 'Routers'
+		routers.expand = (expandedNodeList.indexOf("Routers") > -1)
+		routers.clickFolderMode = 1
 		routers.key = "Routers"
-		$scope.overview.children.push(routers)
-		nodeIds.forEach( function (node) {
-			var name = QDRService.nameFromId(node)
-			var router = new Leaf(name)
-			router.type = "Router"
-			//router.info = routerInfo
-			router.nodeId = node
-			router.key = node
-			routers.children.push(router)
-		})
+		routers.parent = "Routers"
+	    routers.addClass = "routers"
+		topLevelChildren.push(routers)
+		// called when the list of routers changes
+		var updateRouterTree = function (nodes) {
+			var worker = function (node) {
+				var name = QDRService.nameFromId(node)
+				var router = new Folder(name)
+				router.type = "Router"
+				router.info = routerInfo
+				router.nodeId = node
+				router.key = node
+				router.addClass = "router"
+				router.parent = "Routers"
+				router.activate = lastKey === node
+				return router;
+			}
+			updateLeaves(nodes, "Routers", routers, worker)
+		}
+		updateRouterTree(QDRService.nodeIdList());
 
-		var expected = nodeIds.length;
+		// create an addresses tree branch
 		var addresses = new Folder("Addresses")
 		addresses.type = "Addresses"
-		//addresses.info = allAddressInfo
+		addresses.info = allAddressInfo
+		addresses.activate = lastKey === 'Addresses'
+		addresses.expand = (expandedNodeList.indexOf("Addresses") > -1)
+		addresses.clickFolderMode = 1
 		addresses.key = "Addresses"
-		$scope.overview.children.push(addresses)
-
-		var gotAddressFields = function (addressFields) {
-			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
-			}
-			addressFields.forEach( function (address) {
-				var a = new Leaf(address.title)
-				//a.info = addressInfo
+		addresses.parent = "Addresses"
+	    addresses.addClass = "addresses"
+		topLevelChildren.push(addresses)
+		var updateAddressTree = function (addressFields) {
+			var worker = function (address) {
+				var a = new Folder(address.title)
+				a.info = addressInfo
 				a.key = address.uid
 				a.fields = address
 				a.type = "Address"
-				addresses.children.push(a)
-			} )
+				a.addClass = "address"
+				a.activate = lastKey === address.uid
+				a.parent = "Addresses"
+				return a;
+			}
+			updateLeaves(addressFields, "Addresses", addresses, worker)
 		}
-		getAllAddressFields(gotAddressFields)
+		allAddressInfo();
 
+		$scope.setLinkFilter = function (cur) {
+			// filter out non-matching links from the tree and the grid
+			getAllLinkFields([updateLinkGrid, updateLinkTree])
+		}
+		$scope.filterClose = function () {
+			var filter = $('#linkFilter')
+			filter.hide();
+		}
+		$scope.currentLinkFilter = "endpoint"
+		var filterHtml = "<button type='button' class='btn btn-secondary btn-filter'><span class='filter-icon'><i class='icon-filter'> Filter</span></button>";
+		var links = new Folder("Links " + filterHtml)
+		links.type = "Links"
+		links.info = allLinkInfo
+		links.activate = lastKey === 'Links'
+		links.expand = (expandedNodeList.indexOf("Links") > -1)
+		links.clickFolderMode = 1
+		links.key = "Links"
+		links.parent = "Links"
+	    links.addClass = "links"
+		topLevelChildren.push(links)
+
+		// called both before the tree is created and whenever a background update is done
+		var updateLinkTree = function (linkFields) {
+			var worker = function (link) {
+				var l = new Folder(link.title)
+				l.info = linkInfo
+				l.key = link.uid
+				l.fields = link
+				l.type = "Link"
+				l.parent = "Links"
+				l.activate = lastKey === link.uid
+				l.addClass = "link"
+				return l;
+			}
+			linkFields.sort ( function (a,b) { return a.link < b.link ? -1 : a.link > b.link ? 1 : 0})
+			updateLeaves(linkFields, "Links", links, worker)
+		}
+		allLinkInfo();
 
-		var connreceived = 0;
-		var connectionsObj = {}
 		var connections = new Folder("Connections")
 		connections.type = "Connections"
-		//connections.info = allConnectionInfo
+		connections.info = allConnectionInfo
+		connections.activate = lastKey === 'Connections'
+		connections.expand = (expandedNodeList.indexOf("Connections") > -1)
+		connections.clickFolderMode = 1
 		connections.key = "Connections"
-		$scope.overview.children.push(connections)
-		nodeIds.forEach( function (nodeId) {
+		connections.parent = "Connections"
+	    connections.addClass = "connections"
+		topLevelChildren.push(connections)
 
-			QDRService.getNodeInfo(nodeId, ".connection", [], function (nodeName, entity, response) {
-				response.results.forEach( function (result) {
+		updateConnectionTree = function (nodes) {
+			var connectionsObj = {}
+			var expected = nodes.length;
+			var connreceived = 0;
+			nodes.forEach( function (nodeId) {
+				QDRService.getNodeInfo(nodeId, ".connection", [], 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 {
+						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")
-								sasl = "Kerberos"
-							if (sasl === "EXTERNAL")
-								sasl = "x.509"
-							auth = QDRService.valFor(response.attributeNames, result, "user") + "(" +
-									QDRService.valFor(response.attributeNames, result, "sslCipher") + ")"
-							}
-					}
+								sec = "Kerberos"
+							else
+								sec = QDRService.valFor(response.attributeNames, result, "sslProto") + "(" +
+										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")
+						connectionsObj[host] = {}
+						response.attributeNames.forEach( function (attribute, i) {
+							connectionsObj[host][attribute] = result[i]
+						})
+						connectionsObj[host].security = sec
+						connectionsObj[host].authentication = auth
+						connectionsObj[host].routerId = nodeName
 
-					var host = QDRService.valFor(response.attributeNames, result, "host")
-					connectionsObj[host] = {}
-					response.attributeNames.forEach( function (attribute, i) {
-						connectionsObj[host][attribute] = result[i]
 					})
-					connectionsObj[host].security = sec
-					connectionsObj[host].authentication = auth
+					++connreceived;
+					if (connreceived == expected) {
+						var worker = function (connection) {
+							var c = new Folder(connection)
+							c.type = "Connection"
+							c.info = connectionInfo
+							c.key = connection
+							c.fields = connectionsObj[connection]
+							c.tooltip = connectionsObj[connection].role === "inter-router" ? "inter-router connection" : "external connection"
+							c.addClass = c.tooltip
+							c.parent = "Connections"
+							c.activate = lastKey === connection
+							return c
+						}
+						var allConnections = Object.keys(connectionsObj).sort()
+						updateLeaves(allConnections, "Connections", connections, worker)
+					}
 				})
-				++connreceived;
-				if (connreceived == expected) {
-					var allConnections = Object.keys(connectionsObj).sort()
-					allConnections.forEach(function (connection) {
-						var c = new Leaf(connection)
-						c.type = "Connection"
-						c.icon = "ui-icon "
-						c.icon += connectionsObj[connection].role === "inter-router" ? "ui-icon-refresh" : "ui-icon-transfer-e-w"
-						//c.info = connectionInfo
-						c.key = connection
-						c.fields = connectionsObj[connection]
-						c.tooltip = connectionsObj[connection].role === "inter-router" ? "inter-router connection" : "external connection"
-						connections.children.push(c)
-					})
-				}
 			})
-		})
+		}
+		updateConnectionTree(QDRService.nodeIdList())
 
 		var logsreceived = 0;
 		var logObj = {}
 		var logs = new Folder("Logs")
 		logs.type = "Logs"
-		//logs.info = allLogInfo
+		logs.info = allLogInfo
+		logs.activate = lastKey === 'Logs'
+		logs.expand = (expandedNodeList.indexOf("Logs") > -1)
+		logs.clickFolderMode = 1
 		logs.key = "Logs"
-		//$scope.overview.children.push(logs)
+		logs.parent = "Logs"
+		//topLevelChildren.push(logs)
+		var nodeIds = QDRService.nodeIdList()
+		var expected = nodeIds.length;
 		nodeIds.forEach( function (nodeId) {
 			QDRService.getNodeInfo(nodeId, ".log", ["name"], function (nodeName, entity, response) {
 				response.results.forEach( function (result) {
@@ -654,39 +1363,73 @@ var QDR = (function (QDR) {
 				if (logsreceived == expected) {
 					var allLogs = Object.keys(logObj).sort()
 					allLogs.forEach(function (log) {
-						var l = new Leaf(log)
+						var l = new Folder(log)
 						l.type = "Log"
-						//l.info = logInfo
+						l.info = logInfo
 						l.key = log
+						l.parent = "Logs"
+						l.activate = lastKey === log
 						logs.children.push(l)
 					})
-					//console.log("---------------")
-					//console.dump($scope.overview.children)
-					//console.log("---------------")
-					$("#overtree").fancytree({
-						activate: activated,
-						clickFolderMode: 1,
-						source: $scope.overview.children
-						})
-					allRouterInfo();
+					$('#overtree').dynatree({
+						onActivate: activated,
+						onExpand: saveExpanded,
+						onClick: function (n, e) {
+							if (e.target.className.indexOf('-filter') > -1) {
+								//QDR.log.debug("overtree on click called")
+								e.preventDefault();
+								e.stopPropagation()
+								var filter = $('#linkFilter')
+								var treeLink = $('span.links')
+								filter.css({
+                                              top: treeLink.offset().top + treeLink.height(),
+                                              left: treeLink.offset().left,
+                                              zIndex:5000
+                                            });
+								filter.toggle()
+								$("#overtree").dynatree("getTree").getNodeByKey('Links').activate()
+								return false;
+							}
+						},
+						selectMode: 1,
+						debugLevel: 0,
+						activeVisible: false,
+						children: topLevelChildren
+					})
+					treeRoot = $("#overtree").dynatree("getRoot");
+
+					// simulate a click on the previous active node
+					var active = $("#overtree").dynatree("getActiveNode");
+					if (!active) {
+						active = $("#overtree").dynatree("getTree").getNodeByKey("Routers")
+					}
+					activated(active);
+
+					// populate the data for each expanded node
+					updateExpanded();
+					QDRService.addUpdatedAction( "overview", function () {
+						$timeout(updateExpanded);
+					})
+					// update the node list
+					QDRService.startUpdating()
+
+			        loadColState($scope.allRouters);
+			        loadColState($scope.routerGrid);
+			        loadColState($scope.addressesGrid);
+			        loadColState($scope.addressGrid);
+			        loadColState($scope.linksGrid);
+			        loadColState($scope.linkGrid);
+			        loadColState($scope.allConnectionGrid);
+			        loadColState($scope.connectionGrid);
+
 				}
 			})
 		})
 
         $scope.$on("$destroy", function( event ) {
-			if (currentTimer) {
-				clearTimeout(currentTimer)
-				currentTimer = null;
-			}
+			QDRService.stopUpdating()
+			QDRService.delUpdatedAction("overview")
         });
-		var treeTypeMap = {
-			Routers:     allRouterInfo,
-			Router:      routerInfo,
-			Addresses:   allAddressInfo,
-			Address:     addressInfo,
-			Connections: allConnectionInfo,
-			Connection:  connectionInfo
-		}
 
     }]);
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrPlugin.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrPlugin.js b/console/stand-alone/plugin/js/qdrPlugin.js
deleted file mode 100644
index c287342..0000000
--- a/console/stand-alone/plugin/js/qdrPlugin.js
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
-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 entrypoint for the QDR module
- *
- */
-var QDR = (function(QDR) {
-
-  /**
-   * @property pluginName
-   * @type {string}
-   *
-   * The name of this plugin
-   */
-  QDR.pluginName = "QDR";
-
-  /**
-   * @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(QDR.pluginName, ['ngAnimate', 'ngResource', 'ngRoute', 'ui.grid', 'ui.grid.selection',
-    'ui.grid.autoResize', 'jsonFormatter', 'ui.bootstrap', 'ui.slider'/*, 'minicolors' */]);
-
-  // set up the routing for this plugin
-  QDR.module.config(function($routeProvider) {
-    $routeProvider
-      .when('/', {
-        templateUrl: QDR.templatePath + 'qdrConnect.html'
-        })
-      .when('/overview', {
-          templateUrl: QDR.templatePath + 'qdrOverview.html'
-        })
-      .when('/topology', {
-          templateUrl: QDR.templatePath + 'qdrTopology.html'
-        })
-      .when('/list', {
-          templateUrl: QDR.templatePath + 'qdrList.html'
-        })
-      .when('/schema', {
-          templateUrl: QDR.templatePath + 'qdrSchema.html'
-        })
-      .when('/charts', {
-          templateUrl: QDR.templatePath + 'qdrCharts.html'
-        })
-      .when('/connect', {
-          templateUrl: QDR.templatePath + 'qdrConnect.html'
-        })
-  });
-
-  QDR.module.config(['$compileProvider', function ($compileProvider) {
-	var cur = $compileProvider.aHrefSanitizationWhitelist();
-    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|blob):/);
-	cur = $compileProvider.aHrefSanitizationWhitelist();
-  }]);
-
-	QDR.module.config(function (JSONFormatterConfigProvider) {
-		// Enable the hover preview feature
-        JSONFormatterConfigProvider.hoverPreviewEnabled = true;
-	});
-
-	QDR.module.filter('to_trusted', ['$sce', function($sce){
-          return function(text) {
-              return $sce.trustAsHtml(text);
-          };
-    }]);
-
-	QDR.module.filter('humanify', function (QDRService) {
-		return function (input) {
-			return QDRService.humanify(input);
-		};
-	});
-
-	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", "$location", "$log", "QDRService", "QDRChartService",  function ($rootScope, $location, $log, QDRService, QDRChartService) {
-		QDR.log = new QDR.logger($log);
-		QDR.log.debug("QDR.module.run()")
-
-		QDRService.initProton();
-		var settings = angular.fromJson(localStorage[QDR.SETTINGS_KEY]);
-		var lastLocation = localStorage[QDR.LAST_LOCATION];
-		if (!angular.isDefined(lastLocation))
-			lastLocation = "/overview";
-
-		QDRService.addConnectAction(function() {
-			QDRChartService.init(); // initialize charting service after we are connected
-		});
-		if (settings && settings.autostart) {
-			QDRService.addConnectAction(function() {
-				$location.path(lastLocation);
-				$location.replace();
-				$rootScope.$apply();
-			});
-			QDRService.connect(settings);
-        } else {
-			setTimeout(function () {
-	            $location.url('/connect')
-				$location.replace();
-			}, 100)
-        }
-
-        $rootScope.$on('$routeChangeSuccess', function() {
-            localStorage[QDR.LAST_LOCATION] = $location.$$path;
-        });
-
-
-	}]);
-
-	QDR.module.controller ("QDR.MainController", ['$scope', '$location', function ($scope, $location) {
-		QDR.log.debug("started QDR.MainController with location.url: " + $location.url());
-		QDR.log.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.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 || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrSchema.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrSchema.js b/console/stand-alone/plugin/js/qdrSchema.js
index 0e94351..656b159 100644
--- a/console/stand-alone/plugin/js/qdrSchema.js
+++ b/console/stand-alone/plugin/js/qdrSchema.js
@@ -21,9 +21,50 @@ under the License.
  */
 var QDR = (function (QDR) {
 
-    QDR.module.controller("QDR.SchemaController", ['$scope', 'QDRService', function($scope, QDRService) {
+    QDR.module.controller("QDR.SchemaController", ['$scope', '$location', 'QDRService', function($scope, $location, QDRService) {
+		if (!QDRService.connected) {
+			QDRService.redirectWhenConnected("schema")
+			return;
+		}
+		// we are currently connected. setup a handler to get notified if we are ever disconnected
+		QDRService.addDisconnectAction( function () {
+			QDRService.redirectWhenConnected("schema")
+			$scope.$apply();
+		})
+
+        var keys2kids = function (tree, obj) {
+
+			if (obj === Object(obj)) {
+				tree.children = []
+	            for (var key in obj) {
+	                var kid = {title: key}
+	                if (obj[key] === Object(obj[key])) {
+	                    kid.isFolder = true
+	                    keys2kids(kid, obj[key])
+	                } else {
+						kid.title += (': ' + JSON.stringify(obj[key],null,2))
+	                }
+	                tree.children.push(kid)
+	            }
+			}
+        }
+
+		var tree = []
+		for (var key in QDRService.schema) {
+			var kid = {title: key}
+			var val = QDRService.schema[key]
+			if (val === Object(val))
+				keys2kids(kid, val)
+			else
+				kid.title += (': ' + JSON.stringify(val,null,2))
+
+			tree.push(kid);
+		}
+        $('#schema').dynatree({
+			minExpandLevel: 2,
+            children: tree
+        })
 
-        $scope.schema = QDRService.schema;
 
     }]);
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrService.js b/console/stand-alone/plugin/js/qdrService.js
index 758e518..55c6581 100644
--- a/console/stand-alone/plugin/js/qdrService.js
+++ b/console/stand-alone/plugin/js/qdrService.js
@@ -23,7 +23,7 @@ var QDR = (function(QDR) {
 
   // The QDR service handles the connection to
   // the server in the background
-  QDR.module.factory("QDRService", ['$rootScope', '$http', '$resource', '$location', function($rootScope, $http, $resource, $location) {
+  QDR.module.factory("QDRService", ['$rootScope', '$http', '$timeout', '$resource', '$location', function($rootScope, $http, $timeout, $resource, $location) {
     var self = {
 
 	  rhea: require("rhea"),
@@ -57,21 +57,50 @@ var QDR = (function(QDR) {
       executeConnectActions: function() {
         self.connectActions.forEach(function(action) {
           //QDR.log.debug("executing connect action " + action);
-          action.apply();
+			try {
+                action.apply();
+            } catch (e) {
+                // in case the page that registered the handler has been unloaded
+            }
         });
         self.connectActions = [];
+		if ($location.path().startsWith(QDR.pluginRoot)) {
+	        $timeout( function () {
+
+	            var searchObject = $location.search();
+	            var goto = "overview";
+	            if (searchObject.org && searchObject.org !== "connect") {
+	                goto = searchObject.org;
+	            }
+	            $location.search('org', null)
+	            $location.path(QDR.pluginRoot +"/" + goto);
+	        })
+		}
+
       },
       executeDisconnectActions: function() {
         self.disconnectActions.forEach(function(action) {
-          action.apply();
+			try {
+                action.apply();
+            } catch (e) {
+                // in case the page that registered the handler has been unloaded
+            }
         });
         self.disconnectActions = [];
       },
       executeUpdatedActions: function() {
         for (action in self.updatedActions) {
-            self.updatedActions[action].apply();
+			try {
+                self.updatedActions[action].apply();
+            } catch (e) {
+                delete self.updatedActions[action]
+            }
         }
       },
+	redirectWhenConnected: function (org) {
+		$location.path("/" + QDR.pluginName + "/connect")
+		$location.search('org', org);
+	},
 
       notifyTopologyDone: function() {
         //QDR.log.debug("got Toplogy done notice");
@@ -150,12 +179,11 @@ var QDR = (function(QDR) {
     
     onSubscription: function() {
         self.getSchema();
-        self.topology.get();
      },
 
     startUpdating: function () {
-        QDR.log.info("startUpdating called")
         self.stopUpdating();
+        QDR.log.info("startUpdating called")
         self.topology.get();
         self.stop = setInterval(function() {
             self.topology.get();
@@ -191,6 +219,8 @@ var QDR = (function(QDR) {
       },
 
       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(".", " ");
       },
@@ -255,6 +285,44 @@ var QDR = (function(QDR) {
 			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) {
+			// TODO: use connection properties when available
+			if (d.properties.console_identifier == 'Dispatch console')
+				return true;
+			// until connection properties can difinitively identify consoles:
+			var connid = d.connectionId;
+			if (connid && d.nodeType === 'normal') {
+				// find all the endpoint links for this router that have this connid
+				var linkInfo = self.topology.nodeInfo()[d.key]['.router.link']
+				var outs = 0, ins = 0;
+				var outaddr, inaddr;
+				linkInfo.results.forEach( function (link) {
+					if (self.valFor(linkInfo.attributeNames, link, 'connectionId') == connid &&
+						self.valFor(linkInfo.attributeNames, link, 'linkType') == 'endpoint') {
+						if (self.valFor(linkInfo.attributeNames, link, 'linkDir') == 'in') {
+							++ins;
+							inaddr = self.valFor(linkInfo.attributeNames, link, 'owningAddr')
+						}
+						if (self.valFor(linkInfo.attributeNames, link, 'linkDir') == 'out') {
+							++outs;
+							outaddr = self.valFor(linkInfo.attributeNames, link, 'owningAddr')
+						}
+						return true;
+					}
+				})
+				// consoles have 1 out link with an address that starts with Ltemp. and
+				// 1 in link with no address
+				if (outs == 1 && ins == 1 &&
+					inaddr == null && outaddr.startsWith('Ltemp.')) {
+					return true;
+				}
+			}
+			return false;
+		},
+
       /*
        * send the management messages that build up the topology
        *
@@ -329,9 +397,9 @@ var QDR = (function(QDR) {
         // 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.debug("timed out waiting for management responses");
+            QDR.log.info("timed out waiting for management responses");
             // note: can't use 'this' in a timeout handler
-            self.topology.dump("state at timeout");
+            self.topology.miniDump("state at timeout");
             // check if _nodeInfo is consistent
             if (self.topology.isConsistent()) {
                 //TODO: notify controllers which node was dropped
@@ -402,60 +470,31 @@ var QDR = (function(QDR) {
             if (this._expected[id].indexOf(key) == -1)
                 this._expected[id].push(key);
         },
-/*
-The response looks like:
-{
-    ".router": {
-        "results": [
-            [4, "router/QDR.X", 1, "0", 3, 60, 60, 11, "QDR.X", 30, "interior", "org.apache.qpid.dispatch.router", 5, 12, "router/QDR.X"]
-        ],
-        "attributeNames": ["raIntervalFlux", "name", "helloInterval", "area", "helloMaxAge", "mobileAddrMaxAge", "remoteLsMaxAge", "addrCount", "routerId", "raInterval", "mode", "type", "nodeCount", "linkCount", "identity"]
-    },
-    ".connection": {
-        "results": [
-            ["QDR.B", "connection/0.0.0.0:20002", "operational", "0.0.0.0:20002", "inter-router", "connection/0.0.0.0:20002", "ANONYMOUS", "org.apache.qpid.dispatch.connection", "out"],
-            ["QDR.A", "connection/0.0.0.0:20001", "operational", "0.0.0.0:20001", "inter-router", "connection/0.0.0.0:20001", "ANONYMOUS", "org.apache.qpid.dispatch.connection", "out"],
-            ["b2de2f8c-ef4a-4415-9a23-000c2f86e85d", "connection/localhost:33669", "operational", "localhost:33669", "normal", "connection/localhost:33669", "ANONYMOUS", "org.apache.qpid.dispatch.connection", "in"]
-        ],
-        "attributeNames": ["container", "name", "state", "host", "role", "identity", "sasl", "type", "dir"]
-    },
-    ".router.node": {
-        "results": [
-            ["QDR.A", null],
-            ["QDR.B", null],
-            ["QDR.C", "QDR.A"],
-            ["QDR.D", "QDR.A"],
-            ["QDR.Y", "QDR.A"]
-        ],
-        "attributeNames": ["routerId", "nextHop"]
-    }
-}*/
         ondone: function () {
             clearTimeout(this.timerHandle);
             this._gettingTopo = false;
             //this.miniDump();
             //this.dump();
             self.notifyTopologyDone();
-
          },
          dump: function (prefix) {
             if (prefix)
-                QDR.log.debug(prefix);
-            QDR.log.debug("---");
+                QDR.log.info(prefix);
+            QDR.log.info("---");
             for (var key in this._nodeInfo) {
-                QDR.log.debug(key);
+                QDR.log.info(key);
                 console.dump(this._nodeInfo[key]);
-                QDR.log.debug("---");
+                QDR.log.info("---");
             }
             QDR.log.debug("was still expecting:");
             console.dump(this._expected);
         },
          miniDump: function (prefix) {
             if (prefix)
-                QDR.log.debug(prefix);
-            QDR.log.debug("---");
+                QDR.log.info(prefix);
+            QDR.log.info("---");
             console.dump(Object.keys(this._nodeInfo));
-            QDR.log.debug("---");
+            QDR.log.info("---");
         },
         onerror: function (err) {
             this._gettingTopo = false;
@@ -523,16 +562,10 @@ The response looks like:
 			//QDR.log.debug("got all results for  " + entity);
 			// aggregate the responses
 			var newResponse = {};
-			newResponse['aggregates'] = [];
 			var thisNode = responses[selectedNodeId];
-			if (!thisNode) {
-				newResponse['attributeNames'] = ['name'];
-				newResponse['results'] = [''];
-				callback(nodeNames, entity, newResponse);
-				return;
-			}
 			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 = [];
@@ -557,8 +590,8 @@ The response looks like:
 								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]})
 								}
+								aggregate[i].detail.push({node: self.nameFromId(id)+':', val: result[i]})
 							})
 							return true; // stop looping
 						}
@@ -569,7 +602,7 @@ The response looks like:
 						// because it was not in the selectedNodeId's results
 						var vals = [];
 						result.forEach( function (val) {
-							vals.push({sum: val, detail: []})
+							vals.push({sum: val, detail: [{node: self.nameFromId(id), val: val}]})
 						})
 						newResponse.aggregates.push(vals)
 					}
@@ -586,10 +619,24 @@ The response looks like:
             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.schema = angular.copy(response);
-            //self.topology.cleanUp(response);
-            self.notifyTopologyDone();
+	        self.topology.get();
         }, ret.error);
       },
 
@@ -636,8 +683,13 @@ The response looks like:
 			var application_properties = {
 				operation:  operation
 			}
-			if (attrs.type)
-				application_properties.type = attrs.type;
+			var ent = self.schema.entityTypes[entity];
+			var fullyQualifiedType = ent.fullyQualifiedType;
+			if (fullyQualifiedType) {
+				application_properties.type = fullyQualifiedType
+			}
+			else
+				application_properties.type = entity;
 			if (attrs.name)
 				application_properties.name = attrs.name;
 			var msg = {
@@ -676,8 +728,11 @@ The response looks like:
             }
 		if (entity[0] === '.')
 			entity = entity.substr(1, entity.length-1)
-		//return self._send(body, fullAddr, operation, entity);
-		return self._send(body, fullAddr, operation, "org.apache.qpid.dispatch." + entity);
+		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) {
@@ -742,8 +797,8 @@ The response looks like:
 				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")
@@ -757,55 +812,65 @@ The response looks like:
 			}
 			var onDisconnect = function () {
 				//QDR.log.warn("Disconnected");
+				self.connectionError = true;
 				stop();
 				self.executeDisconnectActions();
 			}
 
 			QDR.log.debug("****** calling rhea.connect ********")
-            var connection = self.rhea.connect({
+			var connection;
+			try {
+                connection = self.rhea.connect({
                     connection_details:ws('ws://' + baseAddress, ["binary", "AMQWSB10"]),
                     reconnect:true,
                     properties: {console_identifier: 'Dispatch console'}
-            });
-			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) {
-				onDisconnect();
-				self.errorText = "Error: Connection failed"
-				self.executeDisconnectActions();
-				self.connectionError = true;
-			})
-			connection.on('connection_close', function (context) {
+	            });
+			}
+			catch (e) {
+				QDR.log.debug("exception caught on connect")
+				self.errorText = "Connection failed"
 				onDisconnect();
-				self.errorText = "Disconnected"
-			})
-
-			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();
-			})
+			}
+			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 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);
-			});
+				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);
+				});
+			}
 		}
       }
     }
@@ -818,8 +883,8 @@ The response looks like:
 (function() {
     console.dump = function(object) {
         if (window.JSON && window.JSON.stringify)
-            console.log(JSON.stringify(object,undefined,2));
+            QDR.log.info(JSON.stringify(object,undefined,2));
         else
             console.log(object);
     };
-})();
\ 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


[06/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/index.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/index.html b/console/stand-alone/index.html
index 166d89a..be03d62 100644
--- a/console/stand-alone/index.html
+++ b/console/stand-alone/index.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -17,7 +17,7 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<html>
+<html xmlns:ng="http://angularjs.org">
 
 <head>
 
@@ -25,21 +25,18 @@ under the License.
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <title>Qpid Dispatch Console</title>
 
+    <link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
     <link rel="stylesheet" href="https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.tipsy/1.0.2/jquery.tipsy.css" type="text/css"/>
-
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-grid/3.0.7/ui-grid.css" type="text/css"/>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ng-grid/2.0.11/ng-grid.min.css" type="text/css"/>
     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery.fancytree/2.15.0/skin-win8/ui.fancytree.css" type="text/css">
-
-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
-
+    <link rel="stylesheet" href="https://cdn.rawgit.com/mar10/dynatree/master/dist/skin/ui.dynatree.css" type="text/css">
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" type="text/css">
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css">
 
-
-    <link rel="stylesheet" href="plugin/css/site-base.css" type="text/css"/>
     <link rel="stylesheet" href="plugin/css/plugin.css" type="text/css"/>
-    <link rel="stylesheet" href="plugin/css/qdrTopology.css" type="text/css"/>
+    <link rel="stylesheet" href="plugin/css/dispatch.css" type="text/css"/>
+    <link rel="stylesheet" href="plugin/css/site-base.css" type="text/css"/>
     <link rel="stylesheet" href="https://cdn.rawgit.com/mohsen1/json-formatter/master/dist/json-formatter.min.css" type="text/css"/>
 
 </head>
@@ -69,16 +66,7 @@ under the License.
 
     <div id="main-body" >
         <div id="main" class="container-fluid ng-cloak qdr">
-            <div>
-                <ul class="nav nav-tabs connected" ng-controller="QDR.NavBarController">
-                    <li ng-repeat="link in breadcrumbs" ng-show="isValid(link)" ng-class='{active : isActive(link.href), "pull-right" : isRight(link), haschart: hasChart(link)}'>
-                        <a ng-href="{{link.href}}{{hash}}" ng-bind-html="link.content | to_trusted"></a>
-                    </li>
-                </ul>
-                <div class="row-fluid">
-                    <div ng-view></div>
-                </div>
-            </div>
+            <div ng-include src="'plugin/html/qdrLayout.html'"></div>
         </div>
     </div>
 </div>
@@ -87,27 +75,27 @@ under the License.
     <script src="http://d3js.org/queue.v1.min.js"></script>
     <script src="http://d3js.org/topojson.v0.min.js"></script>
 
-    <script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>
+    <script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
+
+    <script src="https://maxcdn.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script>
 
-    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.5/angular.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.5/angular-sanitize.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.5/angular-resource.min.js"></script>
+
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.4.0/ui-bootstrap-tpls.min.js"></script>
 
-    <script src="https://code.angularjs.org/1.4.8/angular.js"></script>
-    <script src="https://code.angularjs.org/1.4.8/angular-resource.js"></script>
-    <script src="https://code.angularjs.org/1.4.8/angular-route.js"></script>
-    <script src="https://code.angularjs.org/1.4.8/angular-animate.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.1.0/ui-bootstrap-tpls.min.js"></script>
     <script src="https://cdn.rawgit.com/jaz303/tipsy/master/src/javascripts/jquery.tipsy.js"></script>
     <script src="https://cdn.rawgit.com/briancray/tooltipsy/master/tooltipsy.min.js"></script>
     <script src="https://cdn.rawgit.com/angular-ui/ui-slider/master/src/slider.js"></script>
-    <script src="https://cdn.rawgit.com/mohsen1/json-formatter/master/dist/json-formatter.min.js"></script>
-
     <script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-grid/3.0.7/ui-grid.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/ng-grid/2.0.11/ng-grid.min.js"></script>
 
-    <script type="text/javascript" src="plugin/js/qdrPlugin.js"></script>
+    <script type="text/javascript" src="plugin/js/dispatchPlugin.js"></script>
     <script type="text/javascript" src="plugin/js/qdrOverview.js"></script>
     <script type="text/javascript" src="plugin/js/navbar.js"></script>
     <script type="text/javascript" src="plugin/js/qdrList.js"></script>
+    <script type="text/javascript" src="plugin/js/qdrListChart.js"></script>
     <script type="text/javascript" src="plugin/js/qdrCharts.js"></script>
     <script type="text/javascript" src="plugin/js/qdrSchema.js"></script>
     <script type="text/javascript" src="plugin/js/qdrService.js"></script>
@@ -117,7 +105,7 @@ under the License.
 
     <script type="text/javascript" src="plugin/lib/rhea-min.js"></script>
 
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.fancytree/2.15.0/jquery.fancytree.js" type="text/javascript"></script>
+    <script src="https://cdn.rawgit.com/mar10/dynatree/master/dist/jquery.dynatree.min.js"></script>
 
     <script type="text/javascript">
         angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/css/dispatch.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/dispatch.css b/console/stand-alone/plugin/css/dispatch.css
new file mode 100644
index 0000000..e59fa36
--- /dev/null
+++ b/console/stand-alone/plugin/css/dispatch.css
@@ -0,0 +1,640 @@
+/*
+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 .gridStyle {
+    height: inherit !important;
+	min-height: initial !important;
+	overflow: initial;
+}
+
+div#multiple_details, div#link_details {
+	height: 300px;
+	width: 600px;
+	display: none;
+	padding: 0.5em;
+    border: 1px solid;
+	position: absolute;
+	background-color: white;
+	max-height: 330px !important;
+    overflow: hidden;
+}
+div#multiple_details div.ngRow.selected {
+	background-color: #c9dde1 !important;
+}
+
+div.grid-values {
+	text-align: right;
+}
+
+div.grid-values.ngCellText span {
+	padding-right: 4px;
+}
+
+.panel-adjacent {
+  margin-left: 430px;
+}
+
+#topologyForm.selected {
+  border: 1px solid red;
+}
+#topologyForm {
+    border: 1px solid white;
+    padding: 2px;
+    /* position: relative; */
+    /* top: -8px; */
+}
+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:10px; */
+	list-style:none;
+	background:#fff;
+	color:#333;
+    font-weight: 600;
+	/* -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; */
+	-moz-box-shadow:5px 5px 5px #ddd; -webkit-box-shadow:5px 5px 5px #999; box-shadow:5px 5px 5px #ddd;
+	border: 1px solid #aaa;
+}
+.contextMenu ul li {
+	padding:5px 10px;
+	/* border-bottom: solid 1px #ccc; */
+}
+.contextMenu ul li:hover {
+	background:#4a90d9; color:#fff;
+}
+.contextMenu ul li:last-child {
+    border:none;
+}
+
+.na {
+    display: none;
+}
+.contextMenu ul li.new {
+    display: block;
+}
+.contextMenu ul li.adding, .contextMenu ul li.adding + li {
+    display: block;
+}
+.contextMenu ul li.force-display {
+    display: block;
+}
+.contextMenu ul li.context-separator {
+    background-color: lightgray;
+    height: 1px;
+    padding: 0;
+}
+
+.ui-tabs.ui-tabs-vertical .ui-tabs-nav li.separated {
+    margin-top: 1em;
+}
+
+#crosssection {
+    display: none;
+    position: absolute;
+    top: 200px;
+    left: 600px;
+}
+
+.node circle {
+/*  fill: rgb(31, 119, 180);
+  fill-opacity: .25; */
+  fill: #cfe2f3;
+  fill-opacity: .98;
+  stroke: black;
+  stroke-width: 3px;
+}
+
+circle.subcircle {
+    stroke-width: 1px;
+    /* stroke-dasharray: 2; */
+    fill-opacity: 0;
+    stroke: darkgray;
+}
+
+.leaf circle {
+  fill: #6fa8dc;
+  fill-opacity: 0.95;
+  stroke-width: 3px;
+}
+
+.leaf circle[title] {
+    font-family: monospace;
+
+}
+
+#svg_legend {
+    position: absolute;
+    top: 110px;
+    right: 0;
+    border: 1px solid #ccc;
+    border-radius: 5px;
+    background-color: #fcfcfc;
+    margin-right: 1.3em;
+	padding: 1em;
+}
+
+#svg_legend svg {
+    height: 235px;
+    width: 180px;
+}
+
+#multiple_details div.gridStyle {
+/*	height: 50em; */
+	min-height: 70px !important;
+	height: auto !important;
+}
+
+#multiple_details .ngViewport {
+    height: auto !important;
+}
+
+#multiple_details .gridCellButton button, #link_details .gridCellButton button {
+    margin: .25em .4em;
+    font-size: 12px;
+    height: 2em;
+	padding-top: .1em;
+}
+
+#linkFilter {
+	display: none;
+	padding: 0.5em;
+	border: 1px solid grey;
+	background-color: #F0F0F0;
+	position: absolute;
+}
+
+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;
+}
+
+.links button.btn-filter {
+	padding: 0 1em 0 0;
+    margin-left: 1em;
+    font-size: 1em;
+}
+
+button.btn-filter {
+	visibility: hidden;
+}
+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;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/css/plugin.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/plugin.css b/console/stand-alone/plugin/css/plugin.css
index 0d9f34d..db318cf 100644
--- a/console/stand-alone/plugin/css/plugin.css
+++ b/console/stand-alone/plugin/css/plugin.css
@@ -21,37 +21,6 @@ main-display > .span8 {
   position: relative;
 }
 
-.nickname-window,
-.channel-window,
-.main-window {
-  position: absolute;
-  top: 2px;
-  bottom: 2px;
-  left: 4px;
-  right: 4px;
-  /* border: 1px solid #d4d4d4;
-  border-radius: 4px;*/
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-
-.nickname-window > .nickname-window-inner,
-.channel-window > .channel-window-inner,
-.main-window > .main-window-inner {
-  position: static;
-}
-
-.channel-window-inner > ul > li {
-  cursor: pointer;
-  padding: 0;
-  border-bottom: 1px solid #EEEEEE;
-}
-
-
-.channel-window-inner > ul > li > span:hover {
-  background-color: #EAEAEA;
-}
-
 ul.qdrListNodes > li > span {
   padding: 6px 20px; 6px; 6px;
   display: block;
@@ -65,12 +34,11 @@ ul.qdrListNodes > li > span {
 
 
 .qdrList div.gridDetails {
-
     width: auto;
 }
 
 .selectedItems {
-    margin-left: 21em;
+    /* margin-left: 21em; */
 }
 
 .qdrListPane {
@@ -81,93 +49,13 @@ ul.qdrListNodes > li > span {
     width: auto;
 }
 
-.nickname-window-inner > ul > li {
-  cursor: pointer;
-}
-
-.channel-window {
-  bottom: 40px;
-}
-
-.main-window {
-  border: none;
-  top: 46px;
-  bottom: 46px;
-}
-
-.main-window.no-topic {
-  top: 2px;
-}
-
-.topic-window {
-  position: absolute;
-  left: 10px;
-  right: 10px;
-  top: 2px;
-  height: 20px;
-  overflow: hidden;
-  z-index: 50;
-  border-bottom-left-radius: 4px;
-  border-bottom-right-radius: 4px;
-  border-left: 1px solid #d4d4d4;
-  border-right: 1px solid #d4d4d4;
-  border-bottom: 1px solid #d4d4d4;
-  background: white;
-  padding: 10px;
-}
-
-.input-widget {
-  background: white;
-  position: absolute;
-  bottom: 2px;
-  left: 2px;
-  right: 2px;
-  height: 40px;
-  z-index: 50;
-}
-
-.input-widget > form {
-  position: relative;
-}
-
-.input-widget > form > div {
-  position: absolute;
-  left: 2px;
-  top: 2px;
-  bottom: 2px;
-  right: 84px;
-}
-
-.input-widget > form > div > input[type="text"] {
-  width: 100%;
-}
-
-.input-widget > form > input[type="submit"] {
-  position: absolute;
-  right: 2px;
-  top: 3px;
-}
-
-.input-widget > form > input {
-  margin-bottom: 0;
-}
-
-.channel-control-buttons {
-  position: absolute;
-  background: white;
-  bottom: 2px;
-  left: 2px;
-  right: 2px;
-  z-index: 50;
-}
-
 div.listAttrName {
     padding-top: 5px;
 }
 
 div.listAttrName i.icon-bar-chart {
     float: right;
-    margin: 5px;
+    margin: 3px 5px;
 }
 
 div.listAttrName i.icon-bar-chart.active, div.hastip i.icon-bar-chart.active, li.haschart i {
@@ -221,25 +109,48 @@ div.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;
 }
 
-div.d3Chart path {
-    stroke: black;
+/* the x and y axis lines */
+.d3Chart g.axis path.domain {
     stroke-width: 1;
-	opacity: 0.5;
+    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;
+    stroke-width: 1.5;
     fill: none;
     opacity: 1;
 }
@@ -300,6 +211,9 @@ div.d3Chart {
     padding: 1em 0;
     border: 1px solid #C0C0C0;
 }
+div.d3Chart.hDash {
+    border: 0px;
+}
 
 div.d3Chart .axis path {
 	display: inherit;
@@ -342,6 +256,10 @@ div.chartContainer div.aChart {
     margin-top: 0.5em;
 }
 
+div.qdr-attributes .tree-header select {
+	width: 100%;
+}
+
 div#main.qdr div ul.nav li.active a {
   background-color: #e0e0ff !important;
   color: #000000;
@@ -353,9 +271,20 @@ div#main.qdr .selected, .box.selected {
 }
 
 /* the selected node on the list page */
-div.qdrList li.active {
+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-topology.pane.left {
+	width: auto;
+}
 
 /* the selected row in the name table */
 div#main.qdr div.qdrList div.selected {
@@ -431,7 +360,10 @@ div.chartOptions span.minicolors-swatch {
     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;
@@ -468,6 +400,9 @@ div.tabbable .tab-pane {
     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;
@@ -497,15 +432,34 @@ ul.qdrTopoModes {
 	text-align: right;
 }
 
+.grid-align-value .ngCellText {
+	padding-right: 10px;
+}
+
 .overview .ngRow:hover {
 	background:#e0e0ff;
 }
 
+.qdr-overview.pane.left, .qdr-attributes.pane.left {
+	top: 100px;
+}
+.qdr-topology.pane.left {
+	top: 104px;
+}
+.qdr-overview.pane.left, .qdr-attributes.pane.left, .qdr-topology.pane.left {
+	left: 10px;
+}
+
 .treeContainer {
 	width: 250px;
 	float: left;
 }
 
+#entityNames {
+    width: 20em;
+    float: left;
+}
+
 .treeDetails {
 	margin-left: 260px;
 }
@@ -545,8 +499,10 @@ div.chartOptions label:nth-of-type(2) {
 }
 div.chartOptions label {
 	font-weight: normal;
+	display: inline-block;
 }
 
+/*
 .form-horizontal .control-label {
     float: left;
     width: 160px;
@@ -593,16 +549,18 @@ input[type="text"], input[type="number"], input[type="password"] {
 .login input[type="checkbox"] {
 	margin-top: 0.75em;
 }
+*/
 
-#login-container {
-	width: 18.5em;
+#dispatch-login-container {
+	/* width: 18.5em; */
 	margin-top: 2em;
 }
-
+/*
 div.login.container {
-/*	width: 550px; */
-	margin-top: 4em;
+	width: 550px;
 }
+*/
+
 
 #overtree .fancytree-container {
 	border: 0px;
@@ -657,6 +615,201 @@ div.login.container {
   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).connection.external .dynatree-icon:before,
+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: "\f152";
+}
+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).router\.node .dynatree-icon:before {
+     content: "\f013";
+}
+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\.link.inter-router .dynatree-icon:before {
+     content: "\f04e";
+}
+span:not(.dynatree-has-children).router\.link.endpoint .dynatree-icon:before {
+     content: "\f051";
+}
+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).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;
 }
@@ -683,3 +836,39 @@ text.qpid-cpp.on-demand {
     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;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/css/qdrTopology.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/qdrTopology.css b/console/stand-alone/plugin/css/qdrTopology.css
deleted file mode 100644
index f8cffed..0000000
--- a/console/stand-alone/plugin/css/qdrTopology.css
+++ /dev/null
@@ -1,562 +0,0 @@
-/*
-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.qdrTopology div#multiple_details .ngViewport*/
-div#topologyForm .ngViewport, div#topologyForm .gridStyle {
-    height: inherit !important;
-	min-height: initial !important;
-	overflow: initial;
-}
-
-div#multiple_details {
-	height: 300px;
-	width: 500px;
-	display: none;
-	padding: 1em;
-    border: 1px solid;
-	position: absolute;
-	background-color: white;
-	max-height: 330px !important;
-    overflow: hidden;
-}
-
-.panel-adjacent {
-  margin-left: 430px;
-}
-
-#topologyForm.selected {
-  border: 1px solid red;
-}
-#topologyForm {
-    border: 1px solid white;
-    padding: 2px;
-    /* position: relative; */
-    /* top: -8px; */
-}
-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:10px; */
-	list-style:none;
-	background:#fff;
-	color:#333;
-    font-weight: 600;
-	/* -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; */
-	-moz-box-shadow:5px 5px 5px #ddd; -webkit-box-shadow:5px 5px 5px #999; box-shadow:5px 5px 5px #ddd;
-	border: 1px solid #aaa;
-}
-.contextMenu ul li {
-	padding:5px 10px;
-	/* border-bottom: solid 1px #ccc; */
-}
-.contextMenu ul li:hover {
-	background:#4a90d9; color:#fff;
-}
-.contextMenu ul li:last-child {
-    border:none;
-}
-
-.na {
-    display: none;
-}
-.contextMenu ul li.new {
-    display: block;
-}
-.contextMenu ul li.adding, .contextMenu ul li.adding + li {
-    display: block;
-}
-.contextMenu ul li.force-display {
-    display: block;
-}
-.contextMenu ul li.context-separator {
-    background-color: lightgray;
-    height: 1px;
-    padding: 0;
-}
-
-.ui-tabs.ui-tabs-vertical .ui-tabs-nav li.separated {
-    margin-top: 1em;
-}
-
-#crosssection {
-    display: none;
-    position: absolute;
-    top: 200px;
-    left: 600px;
-}
-
-.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: black;
-}
-
-.leaf circle {
-  fill: #6fa8dc;
-  fill-opacity: 0.95;
-  stroke-width: 3px;
-}
-
-.leaf circle[title] {
-    font-family: monospace;
-
-}
-
-#svg_legend {
-    position: absolute;
-    top: 110px;
-    right: 0;
-    border: 1px solid #ccc;
-    border-radius: 5px;
-    background-color: #fcfcfc;
-    margin-right: 1.3em;
-	padding: 1em;
-}
-
-#svg_legend svg {
-    height: 235px;
-    width: 180px;
-}
-
-#multiple_details div.gridStyle {
-/*	height: 50em; */
-	min-height: 70px !important;
-	height: auto !important;
-}
-
-#multiple_details .ngViewport {
-    height: auto !important;
-}
-
-div.topoGrid .ui-grid-viewport {
-	overflow: hidden !important;
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/css/site-base.css
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/css/site-base.css b/console/stand-alone/plugin/css/site-base.css
index e22476b..7c7ebf6 100644
--- a/console/stand-alone/plugin/css/site-base.css
+++ b/console/stand-alone/plugin/css/site-base.css
@@ -21,7 +21,7 @@ under the License.
 }
 
 * {
-    font-family: sans-serif;
+    font-family: 'Open Sans', sans-serif;
 }
 
 a:focus {
@@ -2004,8 +2004,8 @@ div#main div ul.nav li a.nav-primary .caret {
   font-size: 20px;
 }
 
-#main {
-  margin-top: 44px !important;
+div#main.qdr {
+  margin-top: 40px !important;
 }
 
 dd.file-list {
@@ -3344,7 +3344,7 @@ fs-donut svg g text.units {
 
 /* hack to work around strange tabset behavior */
 tabset > .tabbable > ul {
-  display: none;
+/*  display: none; */
 }
 /* end hack */
 
@@ -4504,3 +4504,224 @@ div#main div ul.nav {
     background-repeat: repeat-x;
     box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
 }
+
+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;
+}
+
+.pane {
+    position: fixed;
+    bottom: 0;
+    top: 70px;
+    height: auto;
+    width: 300px;
+}
+
+.pane>.pane-wrapper {
+    position: relative;
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+}
+
+.pane.left .pane-viewport {
+    margin-left: 10px;
+    right: 5px;
+    margin-right: 0;
+}
+
+.pane-viewport {
+    border-top: 1px solid #d4d4d4;
+}
+.pane-viewport {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    overflow: auto;
+    margin-right: 10px;
+}
+
+.pane-content {
+    float: none;
+    position: static;
+}
+.pane-content {
+    width: auto;
+    height: auto;
+}
+
+.pane.left .pane-bar {
+    border-right: 1px solid #d4d4d4;
+}
+.pane.left .pane-bar, .pane.right {
+    right: 0;
+}
+.pane-bar {
+    background: white;
+}
+.pane-bar {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 5px;
+    cursor: ew-resize;
+    height: 100%;
+}
+
+div#overview-controller, div#list-controller {
+	padding-left: 300px;
+}
+
+.ngViewport.ng-scope{
+    height: auto !important;
+    overflow-y: hidden;
+}
+
+.ngTopPanel.ng-scope, .ngHeaderContainer{
+    width: auto !important;
+}
+
+.qdr-overview.pane.left, .qdr-attributes.pane.left, .qdr-topology.pane.left {
+	top: 70px;
+}
+
+div.chartContainer {
+	width: auto;
+}
+
+div.chartOptions div.duration {
+    width: 30em !important;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrCharts.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrCharts.html b/console/stand-alone/plugin/html/qdrCharts.html
index ccb7b5a..dc41a37 100644
--- a/console/stand-alone/plugin/html/qdrCharts.html
+++ b/console/stand-alone/plugin/html/qdrCharts.html
@@ -29,7 +29,7 @@ under the License.
         <div ng-init="chartsLoaded()"></div>
     </div>
     <div ng-hide="hasCharts()" class="centered">
-        There are no charts. To add charts to this page, click on a <i class="icon-bar-chart"></i> icon on the <button ng-click="showListPage()"><i class="icon-list "></i> List</button> page.
+        There are no charts. To add charts to this page, click on a <i class="icon-bar-chart"></i> icon on the <button ng-click="showListPage()"><i class="icon-list "></i> Entities</button> page.
     </div>
 </div>
 
@@ -44,36 +44,37 @@ under the License.
     </div>
     <div class="modal-body">
         <div id="{{svgDivId}}" class="d3Chart"></div>
-        <uib-tabset>
-            <uib-tab heading="Type">
+        <tabset>
+            <tab heading="Type">
                 <legend>Chart type</legend>
-                <label><input type="radio" ng-model="dialogChart.type" value="value" ng-change="chartChanged()" /> Value Chart</label>
-                <label><input type="radio" ng-model="dialogChart.type" value="rate" ng-change="chartChanged()" /> Rate Chart</label>
+                <label><input type="radio" ng-model="dialogChart.type" value="value" /> Value Chart</label>
+                <label><input type="radio" ng-model="dialogChart.type" value="rate" /> Rate Chart</label>
                 <div class="dlg-slider" ng-show="dialogChart.type=='rate'">
                     <span>Rate Window: {{rateWindow}} second{{rateWindow > 1 ? "s" : ""}}</span>
-                    <div class="slider" ui-slider="slider.options" ng-model="rateWindow"></div>
+                    <div id="rateSlider"></div>
                 </div>
                 <div style="clear:both;"> </div>
-            </uib-tab>
-            <uib-tab heading="Colors">
+            </tab>
+            <tab ng-hide="$parent.chart.aggregate()" heading="Colors">
                 <legend>Chart colors</legend>
                 <div class="colorPicker">
-                    <div>Area</div><input minicolors="areaColor" id="position-bottom-left" type="text" ng-model="dialogChart.areaColor" />
+                    <label>Line: <input id="lineColor" name="lineColor" type="color" /></label>
                 </div>
                 <div class="colorPicker">
-                    <div>Line</div><input minicolors="lineColor" id="position-bottom-left" type="text" ng-model="dialogChart.lineColor" />
+                    <label>Area: <input id="areaColor" name="areaColor" type="color" /></label>
                 </div>
                 <div style="clear:both;"> </div>
-            </uib-tab>
-            <uib-tab heading="Duration">
+            </tab>
+            <tab heading="Duration">
                 <legend>Chart duration</legend>
                 <div class="dlg-slider duration">
-                    <span>Show data for past {{dialogChart.visibleDuration}} minute{{dialogChart.visibleDuration > 1 ? "s" : ""}}</span> <div class="slider" ui-slider="duration.options" ng-model="dialogChart.visibleDuration"></div>
+                    <span>Show data for past {{dialogChart.visibleDuration}} minute{{dialogChart.visibleDuration > 1 ? "s" : ""}}</span>
+                    <div id="durationSlider"></div>
                 </div>
                 <div style="clear:both;"> </div>
 
-            </uib-tab>
-        </uib-tabset>
+            </tab>
+        </tabset>
     </div>
     <div class="modal-footer">
         <button class="btn btn-success" type="button" ng-click="apply()">Apply to existing chart</button>
@@ -81,88 +82,5 @@ under the License.
         <button class="btn btn-primary" type="button" ng-click="okClick()">Close</button>
     </div>
 </div>
-<!--
-    <div class="chartOptions" title="Configure chart" ng-controller="QDR.ChartDialogController">
-        <p class="dialogHeader">Title: <input type="text" ng-model="userTitle" /></p>
-        <div id="{{svgDivId}}" class="d3Chart"></div>
-        <p></p>
-        <uib-tabset>
-            <uib-tab heading="Type">
-                <legend>Chart type</legend>
-                <label><input type="radio" ng-model="dialogChart.type" value="value" ng-change="chartChanged()" /> Value Chart</label>
-                <label><input type="radio" ng-model="dialogChart.type" value="rate" ng-change="chartChanged()" /> Rate Chart</label>
-
-                <div class="dlg-slider" ng-show="dialogChart.type=='rate'">
-                    <span>Rate Window: {{rateWindow}} second{{rateWindow > 1 ? "s" : ""}}</span>
-                    <div class="slider" ui-slider="slider.options" ng-model="rateWindow"></div>
-                </div>
-                <div style="clear:both;"> </div>
-            </uib-tab>
-            <uib-tab heading="Colors">
-                <legend>Chart colors</legend>
-                <div class="colorPicker">
-                    <div>Area</div><input minicolors="areaColor" id="position-bottom-left" type="text" ng-model="dialogChart.areaColor" />
-                </div>
-                <div class="colorPicker">
-                    <div>Line</div><input minicolors="lineColor" id="position-bottom-left" type="text" ng-model="dialogChart.lineColor" />
-                </div>
-                <div style="clear:both;"> </div>
-            </uib-tab>
-            <uib-tab heading="Duration">
-                <legend>Chart duration</legend>
-                <div class="dlg-slider duration">
-                    <span>Show data for past {{dialogChart.visibleDuration}} minute{{dialogChart.visibleDuration > 1 ? "s" : ""}}</span> <div class="slider" ui-slider="duration.options" ng-model="dialogChart.visibleDuration"></div>
-                </div>
-                <div style="clear:both;"> </div>
-
-            </uib-tab>
-        </uib-tabset>
-
-        <tabset>
-            <tab heading="Type">
-                <div>
-                    <legend>Chart type</legend>
-                    <label>
-                        <input type="radio" ng-model="dialogChart.type" value="value" ng-change="chartChanged()" />
-                        Value Chart
-                    </label>
-                    <label class="rateGroup">
-                        <input type="radio" ng-model="dialogChart.type" value="rate" ng-change="chartChanged()" /> Rate Chart
-                    </label>
-                    <div class="dlg-slider" ng-show="dialogChart.type=='rate'">
-                        <span>Rate Window: {{rateWindow}} second{{rateWindow > 1 ? "s" : ""}}</span> <div class="slider" ui-slider="slider.options" ng-model="rateWindow"></div>
-                    </div>
-                </div>
-                <div style="clear:both;"> </div>
-            </tab>
-            <tab heading="Colors">
-                <div>
-                    <legend>Chart colors</legend>
-                    <div class="colorPicker">
-                        <div>Area</div><input minicolors="areaColor" id="position-bottom-left" type="text" ng-model="dialogChart.areaColor" />
-                    </div>
-                    <div class="colorPicker">
-                        <div>Line</div><input minicolors="lineColor" id="position-bottom-left" type="text" ng-model="dialogChart.lineColor" />
-                    </div>
-                </div>
-                <div style="clear:both;"> </div>
-            </tab>
-            <tab heading="Duration">
-                <div>
-                    <legend>Chart duration</legend>
-                </div>
-                <div class="dlg-slider duration">
-                    <span>Show data for past {{dialogChart.visibleDuration}} minute{{dialogChart.visibleDuration > 1 ? "s" : ""}}</span> <div class="slider" ui-slider="duration.options" ng-model="dialogChart.visibleDuration"></div>
-                </div>
-                <div style="clear:both;"> </div>
-            </tab>
-        </tabset>
-        <div class="okButton">
-            <button ng-click="apply()">Apply to existing chart</button>
-            <button ng-click="copyToDashboard()">Create new chart</button>
-            <button ng-click="okClick()">Close</button>
-        </div>
-    </div>
--->
 </script>
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrConnect.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrConnect.html b/console/stand-alone/plugin/html/qdrConnect.html
index f1fd0b8..2ea3afa 100644
--- a/console/stand-alone/plugin/html/qdrConnect.html
+++ b/console/stand-alone/plugin/html/qdrConnect.html
@@ -36,66 +36,44 @@ under the License.
                   </div>
               </div>
               <div class="connect-column connect-form">
-                  <div class="span2 offset4">
-                      <!-- <div simple-form name="settings" data="formConfig" entity="formEntity"></div> -->
-                      <form class="form-horizontal no-bottom-margin" novalidate name="settings" action="" method="post">
-                          <fieldset>
-                              <div class="control-group">
-                                  <label class="control-label">Address: </label>
-                                  <div class="controls">
-                                      <input type="text" description="Router address" ng-model="formEntity.address" name="address" required="required" autofocus="autofocus" class="ng-pristine ng-valid ng-valid-required">
-                                      <span class="help-block"></span>
-                                  </div>
+                  <form class="hawtio-form form-horizontal no-bottom-margin" novalidate ng-submit="connect()" name="settings">
+                      <fieldset>
+                          <div class="control-group" ng-show="true">
+                              <label class="strong control-label">Address: </label>
+                              <div class="controls">
+                                  <input tabindex="1" type="text" ng-model="formEntity.address" placeholder="localhost" name="address" autofocus="autofocus" class="ng-pristine ng-valid ng-valid-required"></div>
+                          </div>
+                          <div class="control-group" ng-show="true">
+                              <label tabindex="-1" class="control-label" title="Ports to connect to, by default 5673">Port: </label>
+                              <div class="controls">
+                                  <input tabindex="2" posint type="number" placeholder="5673" tooltip="Ports to connect to, by default 5673" ng-model="formEntity.port" name="port" title="Ports to connect to, by default 5673">
+                                  <span ng-show="settings.port.$error.range">Must be 1 through 65535</span>
                               </div>
-                              <div class="control-group">
-                                  <label class="control-label" title="Port to connect to, by default 5673">Port: </label>
-                                  <div class="controls">
-                                      <input type="number" description="Router port" tooltip="Ports to connect to, by default 5672" ng-model="formEntity.port" name="port" title="Port to connect to, by default 5673" class="ng-scope ng-pristine ng-valid ng-valid-number">
-                                      <span class="help-block"></span>
-                                  </div>
-                              </div>
-                              <!--
-                                              <div class="control-group">
-                                                  <label class="control-label">Username: </label>
-                                                  <div class="controls">
-                                                      <input type="text" description="User Name" ng-model="formEntity.username" name="username" class="ng-pristine ng-valid">
-                                                      <span class="help-block"></span>
-                                                  </div>
-                                              </div>
-                                              <div class="control-group">
-                                                  <label class="control-label">Password: </label>
-                                                  <div class="controls">
-                                                      <input type="password" description="Password" ng-model="formEntity.password" name="password" class="ng-pristine ng-valid">
-                                                      <span class="help-block"></span>
-                                                  </div>
-                                              </div>
-                              -->
-                              <div class="control-group">
-                                  <label class="control-label" title="Whether or not the connection should be started as soon as you navigate to the console">Autostart: </label>
-                                  <div class="controls">
-                                      <input type="checkbox" description="Connect at startup" tooltip="Whether or not the connection should be started as soon as you navigate to the console" ng-model="formEntity.autostart" name="autostart" title="Whether or not the connection should be started as soon as you log into hawtio" class="ng-scope ng-pristine ng-valid">
-                                      <span class="help-block"></span>
-                                  </div>
-                              </div>
-                              <input type="submit" style="position: absolute; left: -9999px; width: 1px; height: 1px;">
-                          </fieldset>
-                      </form>
-                      <p></p>
-                      <div>
-                          <button class="btn btn-primary pull-right" ng-disabled="settings.$invalid" ng-click="connect()">{{buttonText()}}</button>
-                      </div>
-                  </div>
+                          </div>
+                          <div class="control-group" ng-show="true">
+                              <label tabindex="-1" class="control-label" title="Whether or not the connection should be started as soon as you log into hawtio">Autostart: </label>
+                              <div class="controls">
+                                  <input tabindex="3" type="checkbox" tooltip="Whether or not the connection should be started as soon as you log into hawtio" ng-model="formEntity.autostart" name="autostart" title="Whether or not the connection should be started as soon as you log into hawtio"
+                                         class="ng-scope ng-pristine ng-valid"></div>
+                          </div>
+                          <input tabindex="-1" type="submit" style="position: absolute; left: -9999px; width: 1px; height: 1px;">
+                          <p></p>
+                          <div>
+                              <button type="submit" tabindex="4" class="btn btn-primary pull-right" ng-disabled="settings.$invalid">{{buttonText()}}</button>
+                              <!-- <button tabindex="-1" class="btn btn-secondary pull-right" ng-click="connect1()">Debug</button> -->
+                          </div>
+                      </fieldset>
+                  </form>
               </div>
           </div>
       </div>
 
-  <div class="span4 centered" ng-show="connecting">
-    <i class="icon-spin icon-spinner icon-4x"></i>
-    <p>Please wait, connecting now...</p>
-  </div>
-  <div class="centered" ng-show="connectionError">
-    <p>There was a connection error: {{connectionErrorText}}</p>
-  </div>
+      <div class="centered" ng-show="connecting">
+        <i class="icon-spin icon-spinner icon-4x"></i>
+        <p>Please wait, connecting now...</p>
+      </div>
+      <div class="centered" ng-show="connectionError">
+        <p>There was a connection error: {{connectionErrorText}}</p>
+      </div>
 
-  <div class="span4"></div>
 </div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrLayout.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrLayout.html b/console/stand-alone/plugin/html/qdrLayout.html
index d164f8a..208e390 100644
--- a/console/stand-alone/plugin/html/qdrLayout.html
+++ b/console/stand-alone/plugin/html/qdrLayout.html
@@ -16,11 +16,11 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<ul class="nav nav-tabs connected" ng-controller="QDR.NavBarController">
-  <li ng-repeat="link in breadcrumbs" ng-show="isValid(link)" ng-class='{active : isActive(link.href), "pull-right" : isRight(link), haschart: hasChart(link)}'>
-    <a ng-href="{{link.href}}{{hash}}" ng-bind-html="link.content | to_trusted"></a>
+<ul class="nav nav-tabs connected dispatch-view" ng-controller="QDR.NavBarController">
+  <li ng-repeat="link in breadcrumbs" title="{{link.title}}" ng-show="isValid(link)" ng-class='{active : isActive(link.href), "pull-right" : isRight(link), haschart: hasChart(link)}'>
+    <a ng-href="{{link.href}}{{hash}}" ng-bind-html-unsafe="link.content"></a>
   </li>
 </ul>
-<div class="row-fluid">
+<div class="row-fluid dispatch-router">
   <div ng-view></div>
 </div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrList.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrList.html b/console/stand-alone/plugin/html/qdrList.html
index 156b21a..9d1379e 100644
--- a/console/stand-alone/plugin/html/qdrList.html
+++ b/console/stand-alone/plugin/html/qdrList.html
@@ -16,41 +16,73 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<div class="main-display row-fluid qdrList" ng-controller="QDR.ListController">
-    <ul class="nav nav-tabs qdrListNodes">
-          <li ng-repeat="node in nodes" ng-click="selectNode(node)" ng-class="{active : isNodeSelected(node.id)}"> <span>{{node.name}}</span> </li>
-    </ul>
 
+<div id="list-controller" ng-controller="QDR.ListController"  ng-include="tmplListTree"></div>
+
+<script type="text/ng-template" id="listGrid.html">
     <div class="row-fluid qdrListActions">
-        <ul class="nav nav-tabs ng-scope">
-            <li ng-repeat="entity in entities" ng-class="{active : isActionActive(entity.name)}" ng-click="selectAction(entity.name)" >
-                <a title="{{entity.title}}" data-placement="bottom"> {{entity.humanName}} </a></li>
+        <ul class="nav nav-tabs">
+            <li ng-repeat="mode in modes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html-unsafe="mode.content"> </li>
         </ul>
-            <div class="gridStyle" ng-style="getTableHeight()" ui-grid-auto-resize ui-grid-selection ui-grid="gridDef"></div>
-            <div class="selectedItems">
-                <div ng-style="getDetailsTableHeight()" ui-grid-auto-resize ui-grid-selection ui-grid="details"></div>
+        <h4>{{selectedRecordName}}</h4>
+        <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
+            <div ng-show="selectedRecordName === selectedEntity" class="no-content">There are no {{selectedEntity | safePlural}}</div>
+            <div ng-hide="selectedRecordName === selectedEntity" ng-grid="details"></div>
+        </div>
+        <div ng-show="currentMode.id === 'delete'">
+            <div class="delete" ng-show="selectedRecordName !== selectedEntity">
+                <button class="btn btn-primary" ng-click="remove()">Delete</button> {{selectedRecordName}}
             </div>
-    </div>
-</div>
-<!--
-    This is the template for the graph dialog that is displayed. It uses the
-    dialogCtrl controller in qdrList.js.
--->
-<script type="text/ng-template" id="template-from-script.html">
-    <div class="modal-header">
-        <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
-    </div>
-    <div class="modal-body">
-            <div id="{{svgDivId}}" class="d3Chart"></div>
-    </div>
-    <div class="modal-footer">
-        <span ng-hide="isOnChartsPage()">
-            <button class="btn btn-success" type="button" ng-click="addChartsPage()"><i class="icon-bar-chart"></i> Add</button> this chart to the Charts page.
-        </span>
-        <span ng-show="isOnChartsPage()">
-            <button class="btn btn-danger" type="button" ng-click="delChartsPage()">Remove</button> this chart from the <button class="btn btn-success" type="button" ng-click="showChartsPage()"><i class="icon-bar-chart"></i> Charts</button> page.
-        </span>
-        <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
+            <div ng-hide="selectedRecordName !== selectedEntity">
+                There are no {{selectedEntity}}s
+            </div>
+        </div>
+        <div class="operations" ng-show="currentMode.id === 'operations'">
+            <fieldset ng-show="operation != ''">
+                <table>
+                    <tr>
+                        <th>Attribute</th>
+                        <th>Value</th>
+                    </tr>
+                <tr title="{{attribute.title}}" ng-repeat="attribute in detailFields">
+                    <td><label for="{{attribute.name}}">{{attribute.name | humanify}}</label></td>
+                    <!-- we can't do <input type="{angular expression}"> because... jquery throws an exception because... -->
+                    <td>
+                    <div ng-if="attribute.input == 'input'">
+                        <!-- ng-pattern="testPattern(attribute)" -->
+                        <input ng-if="attribute.type == 'number'" type="number" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.rawValue" 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.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"/>
+                        <textarea ng-if="attribute.type == 'textarea'" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.attributeValue" ng-required="attribute.required" class="ui-widget-content ui-corner-all"></textarea>
+                        <span ng-if="attribute.type == 'disabled'" >{{getAttributeValue(attribute)}}</span>
+                    </div>
+                    <div ng-if="attribute.input == 'select'">
+                        <select id="{{attribute.name}}" ng-model="attribute.selected" ng-options="item for item in attribute.rawtype track by item"></select>
+                    </div>
+                    <div ng-if="attribute.input == 'boolean'" class="boolean">
+                        <label><input type="radio" ng-model="attribute.rawValue" ng-value="true"> True</label>
+                        <label><input type="radio" ng-model="attribute.rawValue" ng-value="false"> False</label>
+                    </div>
+                    </td>
+                </tr>
+                <tr><td></td><td><button class="btn btn-primary" type="button" ng-click="ok()">{{operation | Pascalcase}}</button></td></tr>
+                </table>
+            </fieldset>
+        </div>
+        <div ng-show="currentMode.id === 'log'">
+            <table class="log-entry" ng-repeat="entry in logResults track by $index">
+                <tr>
+                    <td align="left" colspan="2">{{entry.time}}</td>
+                </tr>
+                <tr>
+                    <td>Type</td><td>{{entry.type}}</td>
+                </tr>
+                <tr>
+                    <td>Source</td><td>{{entry.source}}:{{entry.line}}</td>
+                </tr>
+                <tr>
+                    <td valign="middle">Message</td><td valign="middle"><pre>{{entry.message}}</pre></td>
+                </tr>
+            </table>
+        </div>
     </div>
 </script>
-

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrOverview.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrOverview.html b/console/stand-alone/plugin/html/qdrOverview.html
index d63ea0e..e3876ee 100644
--- a/console/stand-alone/plugin/html/qdrOverview.html
+++ b/console/stand-alone/plugin/html/qdrOverview.html
@@ -16,61 +16,99 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<div ng-controller="QDR.OverviewController">
+<div id="overview-controller" ng-controller="QDR.OverviewController" ng-include="tmplOverviewTree"></div>
 
-    <div class="treeContainer">
-        <div id="overtree"></div>
+<script type="text/ng-template" id="overviewGrid.html">
+    <div class="gridDetails" ng-include="template.url"></div>
+    <div id="linkFilter">
+        <div class="filter-title">
+            <h6>Filter by link type</h6>
+            <button ng-click="filterClose()" type="button" class="btn btn-primary filter-close">x</button>
+        </div>
+        <select ng-init="currentLinkFilter = 'endpoint'" ng-model="currentLinkFilter" ng-change="setLinkFilter(currentLinkFilter)">
+            <option value="">All link types</option>
+            <option value="endpoint" selected>Endpoints only</option>
+        </select>
     </div>
-
-    <div class="treeDetails" ng-include="template.url"></div>
-</div>
-
-
+</script>
 <!-- the following scripts are content that gets loaded into the above div that has the temple.url -->
 <script type="text/ng-template" id="routers.html">
     <div class="row-fluid">
         <h3>Routers</h3>
         <div class="overview">
-            <div class="gridStyle" ng-style="getGridHeight(allRouters)" ui-grid-auto-resize ui-grid-selection ui-grid="allRouters"></div>
+            <div class="gridStyle" ng-grid="allRouters"></div>
         </div>
     </div>
 </script>
 
 <script type="text/ng-template" id="router.html">
     <div class="row-fluid">
-        <h3>Router {{router.data.title}}</h3>
-        <div ng-style="getGridHeight(routerGrid)" ui-grid-auto-resize ui-grid="routerGrid" class="gridStyle noHighlight"></div>
+        <h3>Router {{router.data.title}} attributes</h3>
+        <div class="gridStyle noHighlight" ng-grid="routerGrid"></div>
     </div>
 </script>
 
 <script type="text/ng-template" id="addresses.html">
     <div class="row-fluid">
-    <h3>Addresses</h3>
-    <div class="overview">
-        <div class="gridStyle" ng-style="getGridHeight(addressGrid)" ui-grid-auto-resize ui-grid-selection ui-grid="addressGrid"></div>
-    </div>
+        <h3>Addresses</h3>
+        <div class="overview">
+            <div class="gridStyle" ng-grid="addressesGrid"></div>
+        </div>
     </div>
 </script>
 <script type="text/ng-template" id="address.html">
     <div class="row-fluid">
-    <h3>Address {{address.data.title}}</h3>
-    <div ng-style="getGridHeight(addressGrid)" ui-grid-auto-resize class="gridStyle noHighlight" ui-grid="addressGrid"></div>
+        <h3>Address {{address.data.title}}</h3>
+        <div class="gridStyle noHighlight" ng-grid="addressGrid"></div>
+    </div>
+</script>
+<script type="text/ng-template" id="links.html">
+    <div class="row-fluid">
+        <h3>Links</h3>
+        <div class="gridStyle" ng-grid="linksGrid"></div>
+    </div>
+</script>
+<script type="text/ng-template" id="link.html">
+    <div class="row-fluid">
+        <h3>Link {{link.data.title}}</h3>
+        <div class="gridStyle noHighlight" ng-grid="linkGrid"></div>
     </div>
 </script>
 <script type="text/ng-template" id="connections.html">
     <div class="row-fluid">
     <h3>Connections</h3>
     <div class="overview">
-        <div class="gridStyle" ng-style="getGridHeight(allConnectionGrid)" ui-grid-auto-resize ui-grid-selection ui-grid="allConnectionGrid"></div>
+        <div class="gridStyle" ng-grid="allConnectionGrid"></div>
     </div>
     </div>
 </script>
 <script type="text/ng-template" id="connection.html">
     <div class="row-fluid">
-    <h3>Connection {{connection.data.title}}</h3>
-    <div ng-style="getGridHeight(connectionGrid)" ui-grid-auto-resize class="gridStyle noHighlight" ui-grid="connectionGrid"></div>
+        <ul class="nav nav-tabs">
+            <li ng-repeat="mode in connectionModes" ng-show="isValid(mode)" ng-click="selectMode(mode)" ng-class="{active : isModeSelected(mode)}" title="{{mode.title}}" ng-bind-html-unsafe="mode.content"> </li>
+        </ul>
+        <div ng-show="currentMode.id === 'attributes'" class="selectedItems">
+            <h3>Connection {{connection.data.title}}</h3>
+            <div class="gridStyle noHighlight" ng-grid="connectionGrid"></div>
+        </div>
+        <div ng-show="currentMode.id === 'links'" class="selectedItems">
+            <h3>External links for connection {{connection.data.title}}</h3>
+            <!-- <button title="{{quiesceState.buttonText}} links" type="button" ng-hide="quiesceHide()" ng-class="quiesceClass()" class="btn" ng-click="quiesceAllClicked()" ng-disabled="quiesceState.buttonDisabled">{{quiesceState.buttonText}}</button> -->
+            <div class="gridStyle noHighlight" ng-grid="connectionLinksGrid"></div>
+        </div>
     </div>
 </script>
+<script type="text/ng-template" id="titleHeaderCellTemplate.html">
+    <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
+        <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
+        <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
+        <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
+        <div class="ngSortPriority">{{col.sortPriority}}</div>
+    </div>
+</script>
+<script type="text/ng-template" id="titleCellTemplate.html">
+    <div title="{{row.entity[col.field]}}" class="ngCellText">{{row.entity[col.field]}}</div>
+</script>
 <script type="text/ng-template" id="logs.html">
     <div class="row-fluid">
     <h3>Logs</h3>
@@ -81,3 +119,21 @@ under the License.
     <h3>Log {{log.data.title}}</h3>
     </div>
 </script>
+
+<script type="text/ng-template" id="linkRowTemplate.html">
+    <div ng-class="{linkDirIn: row.getProperty('linkDir') == 'in', linkDirOut: row.getProperty('linkDir') == 'out'}">
+        <div ng-style="{ 'cursor': row.cursor }" ng-repeat="col in renderedColumns" ng-class="col.colIndex()" class="ngCell {{col.cellClass}}">
+            <div class="ngVerticalBar" ng-style="{height: rowHeight}" ng-class="{ ngVerticalBarVisible: !$last }">&nbsp;</div>
+            <div ng-cell></div>
+        </div>
+    </div>
+</script>
+
+<script type="text/ng-template" id="linkAggTemplate.html">
+    <div ng-click='row.toggleExpand(); saveGroupState()' ng-style='rowStyle(row)' class='ngAggregate ng-scope' style='top: 0px; height: 48px; left: 0px;'>
+    <span class='ngAggregateText ng-binding'>
+            {{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})
+        </span>
+    <div ng-class="{true: 'ngAggArrowCollapsed', false: 'ngAggArrowExpanded'}[row.collapsed]"></div>
+    </div>
+</script>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrSchema.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrSchema.html b/console/stand-alone/plugin/html/qdrSchema.html
index 15ebb46..33f6951 100644
--- a/console/stand-alone/plugin/html/qdrSchema.html
+++ b/console/stand-alone/plugin/html/qdrSchema.html
@@ -17,5 +17,5 @@ specific language governing permissions and limitations
 under the License.
 -->
 <div class="main-display row-fluid" ng-controller="QDR.SchemaController">
-    <json-formatter json="schema" open="2"></json-formatter>
-</div>
\ No newline at end of file
+    <div id="schema"></div>
+</div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/qdrTopology.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/qdrTopology.html b/console/stand-alone/plugin/html/qdrTopology.html
index 2c21b21..3ac2194 100644
--- a/console/stand-alone/plugin/html/qdrTopology.html
+++ b/console/stand-alone/plugin/html/qdrTopology.html
@@ -22,11 +22,11 @@ under the License.
             <!-- <div ng-repeat="form in forms" ng-show="isVisible(form)" ng-class='{selected : isSelected(form)}'> -->
             <div ng-show="form == 'router'">
                 <h4>Router Info</h4>
-                <div class="topoGrid" ng-style="getTableHeight(attributes)" ui-grid-auto-resize ui-grid="topoGridOptions"></div>
+                <div class="gridStyle" ng-grid="topoGridOptions"></div>
             </div>
             <div ng-show="form == 'connection'">
                 <h4>Connection Info</h4>
-                <div class="topoGrid" ng-style="getTableHeight(attributes)" ui-grid-auto-resize ui-grid="topoGridOptions"></div>
+                <div class="gridStyle" ng-grid="topoGridOptions"></div>
             </div>
             <div id="addNodeForm" ng-show="form == 'add'">
                 <h4>Add a new router</h4>
@@ -74,9 +74,26 @@ under the License.
         <div id="svg_legend"></div>
         <div id="multiple_details">
             <div class="gridStyle" ng-grid="multiDetails"></div>
+         </div>
+        <div id="link_details">
+            <div class="gridStyle" ng-grid="linkDetails"></div>
         </div>
     </div>
 </div>
+
+
+<script type="text/ng-template" id="titleHeaderCellTemplate.html">
+    <div title="{{col.displayName}}" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{'cursor': col.cursor}" ng-class="{ 'ngSorted': !noSortVisible }">
+        <div ng-click="col.sort($event)" ng-class="'colt' + col.index" class="ngHeaderText">{{col.displayName}}</div>
+        <div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div>
+        <div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div>
+        <div class="ngSortPriority">{{col.sortPriority}}</div>
+    </div>
+</script>
+<script type="text/ng-template" id="titleCellTemplate.html">
+    <div title="{{row.entity[col.field]}}" class="ngCellText">{{row.entity[col.field]}}</div>
+</script>
+
 <!--
     This is the template for the node edit dialog that is displayed.
 -->
@@ -86,21 +103,21 @@ under the License.
     </div>
     <div class="modal-body">
         <form novalidate name="editForm">
-        <div class="tabbable tabs-left">
-            <ul class="nav nav-tabs">
-                <li ng-repeat="entity in entities" ng-click="setActive(entity.tabName)" ng-class="{separated: entity.tabName == 'listener0'}">
-                    <a href="#{{entity.tabName}}" data-toggle="tab"><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}}</a>
-                </li>
-            </ul>
-            <div class="tab-content">
-                <div id="{{entity.tabName}}" ng-repeat="entity in entities" ng-show="isActive(entity.tabName)" class="entity-fields">
+
+            <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 type="{{attribute.type}}" 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 == '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>
@@ -119,63 +136,16 @@ under the License.
                     <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>
-                </div>
-            </div>
-        </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>
-
-<!--
-
-
-    <div class="nodeOptions" title="Configure new router">
-        <form novalidate name="editForm">
-        <div id="tabs" class="nodeEntities">
-            <ul class="nodeTabs">
-                <li ng-repeat="entity in entities" ng-class="{separated: entity.tabName == 'listener0'}">
-
-                    <a href="#{{entity.tabName}}"><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}}</a>
-                </li>
-            </ul>
-            <div id="{{entity.tabName}}" ng-repeat="entity in entities" class="entity-fields">
-                <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>
-                        <div ng-if="attribute.input == 'input'">
-                            <input type="{{attribute.type}}" name="{{attribute.name}}" id="{{attribute.name}}" ng-model="attribute.value" ng-pattern="testPattern(attribute)" 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>
-            </div>
-        </div>
-        </form>
-
-        <div class="okButton">
-            <button ng-click="download()">Download</button>
-            <button ng-click="cancel()">Cancel</button>
-        </div>
-    </div>
-    -->
 </script>
 
 <script type="text/ng-template" id="config-file-header.html">##
@@ -199,6 +169,32 @@ under the License.
 </script>
 
 <script type="text/ng-template" id="download-dialog-template.html">
+    <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>
+
+<!--
+
     <div title="Configure new router">
 
         <label title="Show descriptions and default values in confile files"><input type="checkbox" ng-model="verbose"> Verbose output</label>
@@ -220,5 +216,6 @@ under the License.
         </div>
 
     </div>
+-->
 </script>
 


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


[04/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrList.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrList.js b/console/stand-alone/plugin/js/qdrList.js
index 7352d82..c752a8f 100644
--- a/console/stand-alone/plugin/js/qdrList.js
+++ b/console/stand-alone/plugin/js/qdrList.js
@@ -28,36 +28,105 @@ var QDR = (function(QDR) {
    *
    * Controller for the main interface
    */
-	QDR.module.controller("QDR.ListController", ['$scope', '$location', 'uiGridConstants', 'QDRService', 'QDRChartService', '$uibModal',
-		function ($scope, $location, uiGridConstants, QDRService, QDRChartService, $uibModal) {
+	QDR.module.controller("QDR.ListController", ['$scope', '$location', '$dialog', '$filter', 'QDRService', 'QDRChartService',
+		function ($scope, $location, $dialog, $filter, QDRService, QDRChartService) {
 
-		//QDR.log.debug("started List controller");
-		if (!angular.isDefined(QDRService.schema))
-			return;
-		$scope.selectedAction = localStorage['QDRSelectedAction'];
+		var updateIntervalHandle = undefined;
+		var updateInterval = 5000;
+		var ListExpandedKey = "QDRListExpanded";
+		$scope.details = {};
+
+		$scope.tmplListTree = QDR.templatePath + 'tmplListTree.html';
+		$scope.selectedEntity = localStorage['QDRSelectedEntity'] || "address";
 		$scope.selectedNode = localStorage['QDRSelectedNode'];
 		$scope.selectedNodeId = localStorage['QDRSelectedNodeId'];
 		$scope.selectedRecordName = localStorage['QDRSelectedRecordName'];
-
-		var excludedEntities = ["management", "org.amqp.management", "operationalEntity", "entity", "configurationEntity", "dummy", "console"];
-		var aggregateEntities = ["router.address"];
-
-		$scope.entities = [];
-		for (var entity in QDRService.schema.entityTypes) {
-			if (excludedEntities.indexOf(entity) == -1) {
-				$scope.entities.push( {
-					title: angular.isDefined(QDRService.schema.entityTypes[entity].description) ? QDRService.schema.entityTypes[entity].description : '',
-					humanName: QDRService.humanify(entity),
-					name: entity}
-				 );
+		$scope.nodes = []
+		$scope.currentNode = undefined;
+		$scope.modes = [{
+	        content: '<a><i class="icon-list"></i> Attributes</a>',
+			id: 'attributes',
+			op: 'READ',
+			title: "View router attributes",
+	        isValid: function () { return true; }
+	    },
+	    {
+	        content: '<a><i class="icon-edit"></i> Update</a>',
+	        id: 'operations',
+	        op: 'UPDATE',
+	        title: "Update this attribute",
+	        isValid: function () { return $scope.operations.indexOf(this.op) > -1 }
+	    },
+	    {
+	        content: '<a><i class="icon-plus"></i> Create</a>',
+	        id: 'operations',
+	        op: 'CREATE',
+	        title: "Create a new attribute",
+	        isValid: function () { return $scope.operations.indexOf(this.op) > -1 }
+	    },
+	    {
+	        content: '<a><i class="icon-remove"></i> Delete</a>',
+	        id: 'delete',
+	        op: 'DELETE',
+	        title: "Delete",
+	        isValid: function () { return $scope.operations.indexOf(this.op) > -1 }
+	    },
+	    {
+	        content: '<a><i class="icon-eye-open"></i> Fetch</a>',
+	        id: 'log',
+	        op: 'GET-LOG',
+	        title: "Fetch recent log entries",
+	        isValid: function () { return ($scope.selectedEntity === 'log') }
+	    }
+	    ];
+		$scope.operations = []
+        $scope.currentMode = $scope.modes[0];
+		$scope.isModeSelected = function (mode) {
+			return mode === $scope.currentMode;
+		}
+		$scope.selectMode = function (mode) {
+			$scope.currentMode = mode;
+			if (mode.id === 'log') {
+				$scope.logResults = "getting recent log entries...";
+				QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, {}, $scope.currentMode.op, function (nodeName, entity, response, context) {
+					$scope.logResults = response.filter( function (entry) {
+						return entry[0] === $scope.detailsObject.module
+					}).sort( function (a, b) {
+						return b[5] - a[5]
+					}).map( function (entry) {
+						return {
+							type: entry[1],
+							message: entry[2],
+							source: entry[3],
+							line: entry[4],
+							time: Date(entry[5]).toString()
+						}
+					})
+					$scope.$apply();
+				})
 			}
 		}
-		if (!angular.isDefined($scope.selectedAction)) {
-			$scope.selectedAction = $scope.entities[0].name;
-			//QDR.log.debug("defaulted selectedAction to " + $scope.selectedAction);
+		$scope.isValid = function (mode) {
+			return mode.isValid()
 		}
 
+		if (!QDRService.connected) {
+			// we are not connected. we probably got here from a bookmark or manual page reload
+			QDRService.redirectWhenConnected("list");
+			return;
+		}
+		// we are currently connected. setup a handler to get notified if we are ever disconnected
+		QDRService.addDisconnectAction( function () {
+			QDRService.redirectWhenConnected("list")
+			$scope.$apply();
+		})
+
 		$scope.nodes = QDRService.nodeList().sort(function (a, b) { return a.name.toLowerCase() > b.name.toLowerCase()});
+		// unable to get node list? Bail.
+		if ($scope.nodes.length == 0) {
+			$location.path("/" + QDR.pluginName + "/connect")
+			$location.search('org', "list");
+		}
 		if (!angular.isDefined($scope.selectedNode)) {
 			//QDR.log.debug("selectedNode was " + $scope.selectedNode);
 			if ($scope.nodes.length > 0) {
@@ -66,124 +135,417 @@ var QDR = (function(QDR) {
 				//QDR.log.debug("forcing selectedNode to " + $scope.selectedNode);
 			}
 		}
+		var setCurrentNode = function () {
+			$scope.nodes.some( function (node, i) {
+				if (node.name === $scope.selectedNode) {
+					$scope.currentNode = $scope.nodes[i]
+					return true;
+				}
+			})
+		}
+		setCurrentNode();
+		if ($scope.currentNode == undefined) {
+			if ($scope.nodes.length > 0) {
+				$scope.selectedNode = $scope.nodes[0].name;
+				$scope.selectedNodeId = $scope.nodes[0].id;
+				$scope.currentNode = $scope.nodes[0];
+			}
+		}
 
-		$scope.isActionActive = function(name) {
-			//QDR.log.debug("isActionActive(" + name + ")  selectedAction is " + $scope.selectedAction);
-			return $scope.selectedAction === name;
-		};
-		$scope.isNodeSelected = function (id) {
-			return $scope.selectedNodeId === id;
+		var excludedEntities = ["management", "org.amqp.management", "operationalEntity", "entity", "configurationEntity", "dummy", "console"];
+		var aggregateEntities = ["router.address"];
+		var classOverrides = {
+			"connection": function (row) {
+				return row.role.value;
+			},
+			"router.link": function (row) {
+				return row.linkType.value;
+			}
+		}
+
+		var lookupOperations = function () {
+			var ops = QDRService.schema.entityTypes[$scope.selectedEntity].operations.filter( function (op) { return op !== 'READ'});
+			$scope.operation = ops.length ? ops[0] : "";
+			return ops;
+		}
+
+        var entityTreeChildren = [];
+        var expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
+        var sortedEntities = Object.keys(QDRService.schema.entityTypes).sort();
+		sortedEntities.forEach( function (entity) {
+			if (excludedEntities.indexOf(entity) == -1) {
+				if (!angular.isDefined($scope.selectedEntity)) {
+					$scope.selectedEntity = entity;
+					$scope.operations = lookupOperations()
+				}
+				var e = new Folder(entity)
+				e.typeName = "entity"
+                e.key = entity
+				e.expand = (expandedList.indexOf(entity) > -1)
+				var placeHolder = new Folder("loading...")
+				placeHolder.addClass = "loading"
+			    e.children = [placeHolder]
+				entityTreeChildren.push(e)
+			}
+		})
+		$scope.treeReady = function () {
+			$('#entityTree').dynatree({
+				onActivate: onTreeSelected,
+				onExpand: onTreeNodeExpanded,
+				selectMode: 1,
+				activeVisible: false,
+				debugLevel: 0,
+				children: entityTreeChildren
+			})
+			restartUpdate();    // start getting the data now that the tree is created
+			updateExpandedEntities();
 		};
+		var onTreeNodeExpanded = function (expanded, node) {
+			// save the list of entities that are expanded
+			var tree = $("#entityTree").dynatree("getTree");
+			var list = [];
+			tree.visit( function (tnode) {
+				if (tnode.isExpanded()) {
+					list.push(tnode.data.key)
+				}
+			})
+			localStorage[ListExpandedKey] = JSON.stringify(list)
+
+			if (expanded)
+				onTreeSelected(node);
+		}
+		// a tree node was selected
+		var onTreeSelected = function (selectedNode) {
+			if ($scope.currentMode.id === 'operations')
+				$scope.currentMode = $scope.modes[0];
+			else if ($scope.currentMode.id === 'log')
+				$scope.selectMode($scope.currentMode)
+			else if ($scope.currentMode.id === 'delete') {
+				// clicked on a tree node while on the delete screen -> switch to attribute screen
+				$scope.currentMode = $scope.modes[0];
+			}
+			if (selectedNode.data.typeName === "entity") {
+				$scope.selectedEntity = selectedNode.data.key;
+				$scope.operations = lookupOperations()
+			} else if (selectedNode.data.typeName === 'attribute') {
+				$scope.selectedEntity = selectedNode.parent.data.key;
+				$scope.operations = lookupOperations()
+				$scope.selectedRecordName = selectedNode.data.key;
+				updateDetails(selectedNode.data.details);   // update the table on the right
+				$("#entityTree").dynatree("getRoot").visit(function(node){
+				   node.select(false);
+				});
+				selectedNode.select();
+			}
+			$scope.$apply();
+		}
+		// fill in an empty results recoord based on the entities schema
+		var fromSchema = function (entityName) {
+			var row = {}
+			var schemaEntity = QDRService.schema.entityTypes[entityName]
+			for (attr in schemaEntity.attributes) {
+				var entity = schemaEntity.attributes[attr]
+				var value = ""
+				if (angular.isDefined(entity['default']))
+					value = entity['default']
+				row[attr] = {
+					value: value,
+					type: entity.type,
+					graph: false,
+					title: entity.description,
+					aggregate: false,
+					aggregateTip: '',
+					'default': entity['default']
+				}
+			}
+			return row;
+		}
+		$scope.hasCreate = function () {
+			var schemaEntity = QDRService.schema.entityTypes[$scope.selectedEntity]
+			return (schemaEntity.operations.indexOf("CREATE") > -1)
+		}
+
+		var stopUpdating = function () {
+			if (angular.isDefined(updateIntervalHandle)) {
+				clearInterval(updateIntervalHandle);
+			}
+			updateIntervalHandle = undefined;
+		}
+
+		// the data for the selected entity is available, populate the tree
+		var updateEntityChildren = function (entity, tableRows, expand) {
+			var tree = $("#entityTree").dynatree("getTree");
+			if (!tree.getNodeByKey) {
+				return stopUpdating()
+			}
+			var node = tree.getNodeByKey(entity)
+			var updatedDetails = false;
+			var scrollTreeDiv = $('.qdr-attributes.pane.left .pane-viewport')
+			var scrollTop = scrollTreeDiv.scrollTop();
+			node.removeChildren();
+			if (tableRows.length == 0) {
+			    node.addChild({
+					addClass:   "no-data",
+			        typeName:   "none",
+			        title:      "no data",
+					key:        node.data.key + ".1"
+			    })
+			    if (expand) {
+			        updateDetails(fromSchema(entity));
+       			    $scope.selectedRecordName = entity;
+				}
+			} else {
+				tableRows.forEach( function (row) {
+					var addClass = entity;
+					if (classOverrides[entity]) {
+						addClass += " " + classOverrides[entity](row);
+					}
+					var child = {
+                        typeName:   "attribute",
+                        addClass:   addClass,
+                        key:        row.name.value,
+                        title:      row.name.value,
+                        details:    row
+                    }
+					if (row.name.value === $scope.selectedRecordName) {
+						if (expand)
+							updateDetails(row); // update the table on the right
+						child.select = true;
+						updatedDetails = true;
+					}
+					node.addChild(child)
+				})
+			}
+			// if the selectedRecordName was not found, select the 1st one
+			if (expand && !updatedDetails && tableRows.length > 0) {
+				var row = tableRows[0];
+				$scope.selectedRecordName = row.name.value;
+				var node = tree.getNodeByKey($scope.selectedRecordName);
+				node.select(true);
+				updateDetails(row)  // update the table on the right
+			}
+			scrollTreeDiv.scrollTop(scrollTop)
+		}
+
+		var schemaProps = function (entityName, key, currentNode) {
+	   		var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
+
+			var entity = QDRService.schema.entityTypes[entityName]
+			var value = entity.attributes[key]
+			// skip identity and depricated fields
+			if (!value)
+				return {input: 'input', type: 'disabled', required: false, selected: "", rawtype: 'string', disabled: true, 'default': ''}
+			var description = value.description || ""
+			var val = value['default'];
+			var disabled = (key == 'identity' || description.startsWith('Deprecated'))
+			// special cases
+			if (entityName == 'log' && key == 'module') {
+				return {input: 'input', type: 'disabled', required: false, selected: "", rawtype: 'string', disabled: true, 'default': ''}
+			}
+			if (entityName === 'linkRoutePattern' && key === 'connector') {
+				// turn input into a select. the values will be populated later
+				value.type = []
+				// find all the connector names and populate the select
+				QDRService.getNodeInfo(currentNode.id, '.connector', ['name'], function (nodeName, dotentity, response) {
+					$scope.detailFields.some( function (field) {
+						if (field.name === 'connector') {
+							field.rawtype = response.results.map (function (result) {return result[0]})
+							return true;
+						}
+					})
+				});
+			}
+			return {    name:       key,
+						humanName:  QDRService.humanify(key),
+                        description:value.description,
+                        type:       disabled ? 'disabled' : typeMap[value.type],
+                        rawtype:    value.type,
+                        input:      typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input'
+                                                                  : 'select',
+                        selected:   val ? val : undefined,
+                        'default':  value['default'],
+                        value:      val,
+                        required:   value.required,
+                        unique:     value.unique,
+                        disabled:   disabled
+            };
+		}
+		$scope.getAttributeValue = function (attribute) {
+			var value = attribute.attributeValue;
+			if ($scope.currentMode.op === "CREATE" && attribute.name === 'identity')
+				value = "<assigned by system>"
+			return value;
+		}
+
+		// update the table on the right
+		var updateDetails = function (row) {
+			var details = [];
+			$scope.detailsObject = {};
+			var attrs = Object.keys(row).sort();
+			attrs.forEach( function (attr) {
+				var changed = $scope.detailFields.filter(function (old) {
+					return (old.name === attr) ? old.graph && old.rawValue != row[attr].value : false;
+				})
+				var schemaEntity = schemaProps($scope.selectedEntity, attr, $scope.currentNode)
+				details.push( {
+					attributeName:  QDRService.humanify(attr),
+					attributeValue: attr === 'port' ? row[attr].value : QDRService.pretty(row[attr].value),
+					name:           attr,
+					changed:        changed.length,
+					rawValue:       row[attr].value,
+					graph:          row[attr].graph,
+					title:          row[attr].title,
+					aggregateValue: QDRService.pretty(row[attr].aggregate),
+					aggregateTip:   row[attr].aggregateTip,
+
+					input:          schemaEntity.input,
+					type:           schemaEntity.type,
+					required:       schemaEntity.required,
+					selected:       schemaEntity.selected,
+					rawtype:        schemaEntity.rawtype,
+					disabled:       schemaEntity.disabled,
+					'default':      schemaEntity['default']
+				})
+				$scope.detailsObject[attr] = row[attr].value;
+			})
+			setTimeout(applyDetails, 1, details)
+		}
+
+		var applyDetails = function (details) {
+			$scope.detailFields = details;
+			aggregateColumn();
+			$scope.$apply();
+			// ng-grid bug? the entire table doesn't always draw unless a reflow is triggered;
+			$(window).trigger('resize');
+		}
+
+		var restartUpdate = function () {
+			stopUpdating();
+			updateTableData($scope.selectedEntity, true);
+			updateIntervalHandle = setInterval(updateExpandedEntities, updateInterval);
+		}
+		var updateExpandedEntities = function () {
+			var tree = $("#entityTree").dynatree("getTree");
+			if (tree.visit) {
+				tree.visit( function (node) {
+					if (node.isExpanded()) {
+						updateTableData(node.data.key, node.data.key === $scope.selectedEntity)
+					}
+				})
+			} else {
+				stopUpdating();
+			}
+		}
 
 		$scope.selectNode = function(node) {
-			//QDR.log.debug("setting selectedNode to " + node.name);
 			$scope.selectedNode = node.name;
 			$scope.selectedNodeId = node.id;
-			//QDR.log.debug("location is " + $location.url());
-			$location.search('n', node.name);
-		};
-		$scope.selectAction = function(action) {
-			$scope.selectedAction = action;
-			$location.search('a', action);
-			//QDR.log.debug("selectAction called with " + action + "  location is now " + $location.url());
+			setCurrentNode();
+			restartUpdate();
 		};
-
-		$scope.$watch('selectedAction', function(newValue, oldValue) {
+		$scope.$watch('selectedEntity', function(newValue, oldValue) {
 			if (newValue !== oldValue) {
-				localStorage['QDRSelectedAction'] = $scope.selectedAction;
-				//QDR.log.debug("saving selectedAction as " + $scope.selectedAction + " newValue is " + newValue);
+				localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
+				restartUpdate();
+				$scope.operations = lookupOperations()
 			}
 		})
 		$scope.$watch('selectedNode', function(newValue, oldValue) {
 		    if (newValue !== oldValue) {
 				localStorage['QDRSelectedNode'] = $scope.selectedNode;
 				localStorage['QDRSelectedNodeId'] = $scope.selectedNodeId;
-				//QDR.log.debug("saving selectedNode as " + $scope.selectedNode + " newValue is " + newValue);
 			}
 		})
 		$scope.$watch('selectedRecordName', function(newValue, oldValue) {
 			if (newValue != oldValue) {
 				localStorage['QDRSelectedRecordName'] = $scope.selectedRecordName;
-				//QDR.log.debug("saving selectedRecordName as " + $scope.selectedRecordName);
 			}
 		})
 
-		$scope.tableRows = [];
-		var selectedRowIndex = 0;
-		var updateTableData = function (entity) {
-			var gotNodeInfo = function (nodeName, dotentity, response) {
-				//QDR.log.debug("got results for  " + nodeName);
-				//console.dump(response);
+		/* Called periodically to refresh the data on the page */
+		var updateTableData = function (entity, expand) {
+			if (!QDRService.connected) {
+				// we are no longer connected. bail back to the connect page
+				$location.path("/" + QDR.pluginName + "/connect")
+				$location.search('org', "list");
+				return;
+			}
+			// don't update the data when on the operations tab
+			if ($scope.currentMode.id === 'operations') {
+				return;
+			}
 
+			var gotNodeInfo = function (nodeName, dotentity, response) {
+				var tableRows = [];
 				var records = response.results;
 				var aggregates = response.aggregates;
 				var attributeNames = response.attributeNames;
-				var nameIndex = attributeNames.indexOf("name");
-				var ent = QDRService.schema.entityTypes[entity];
-				var tableRows = [];
-				for (var i=0; i<records.length; ++i) {
-					var record = records[i];
-					var aggregate = aggregates ? aggregates[i] : undefined;
-					var row = {};
-					var rowName;
-					if (nameIndex > -1) {
-						rowName = record[nameIndex];
-					} else {
-						QDR.log.error("response attributeNames did not contain a name field");
-						console.dump(response.attributeNames);
-						return;
-					}
-					if (rowName == $scope.selectedRecordName)
-						selectedRowIndex = i;
-					for (var j=0; j<attributeNames.length; ++j) {
-						var col = attributeNames[j];
-						row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
-						if (ent) {
-							var att = ent.attributes[col];
-							if (att) {
-								row[col].type = att.type;
-								row[col].graph = att.graph;
-								row[col].title = att.description;
-
-								if (aggregate) {
-									if (att.graph) {
-										row[col].aggregate = att.graph ? aggregate[j].sum : '';
-										var tip = [];
-										aggregate[j].detail.forEach( function (line) {
-											tip.push(line);
-										})
-										row[col].aggregateTip = angular.toJson(tip);
+				// If !attributeNmes then  there was an error getting the records for this entity
+				if (attributeNames) {
+					var nameIndex = attributeNames.indexOf("name");
+					var identityIndex = attributeNames.indexOf("identity");
+					var ent = QDRService.schema.entityTypes[entity];
+					for (var i=0; i<records.length; ++i) {
+						var record = records[i];
+						var aggregate = aggregates ? aggregates[i] : undefined;
+						var row = {};
+						var rowName;
+						if (nameIndex > -1) {
+							rowName = record[nameIndex];
+							if (!rowName && identityIndex > -1) {
+								rowName = record[nameIndex] = (dotentity + '/' + record[identityIndex])
+							}
+						}
+						if (!rowName) {
+							QDR.log.error("response attributeNames did not contain a name field");
+							console.dump(response.attributeNames);
+							return;
+						}
+						for (var j=0; j<attributeNames.length; ++j) {
+							var col = attributeNames[j];
+							row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
+							if (ent) {
+								var att = ent.attributes[col];
+								if (att) {
+									row[col].type = att.type;
+									row[col].graph = att.graph;
+									row[col].title = att.description;
+
+									if (aggregate) {
+										if (att.graph) {
+											row[col].aggregate = att.graph ? aggregate[j].sum : '';
+											var tip = [];
+											aggregate[j].detail.forEach( function (line) {
+												tip.push(line);
+											})
+											row[col].aggregateTip = angular.toJson(tip);
+										}
 									}
 								}
 							}
 						}
+						tableRows.push(row);
 					}
-					tableRows.push(row);
 				}
-				setTimeout(selectRow, 0, tableRows);
-			}
 
+				tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value) })
+				setTimeout(selectRow, 0, {entity: dotentity, rows: tableRows, expand: expand});
+			}
 			// if this entity should show an aggregate column, send the request to get the info for this entity from all the nedes
 			if (aggregateEntities.indexOf(entity) > -1) {
 				var nodeInfo = QDRService.topology.nodeInfo();
 				QDRService.getMultipleNodeInfo(Object.keys(nodeInfo), entity, [], gotNodeInfo, $scope.selectedNodeId);
 			} else {
-				QDRService.getNodeInfo($scope.selectedNodeId, '.' + entity, [], gotNodeInfo);
+				QDRService.getNodeInfo($scope.selectedNodeId, entity, [], gotNodeInfo);
 			}
 		};
 
 		// tableRows are the records that were returned, this populates the left hand table on the page
-		var selectRow = function (tableRows) {
-			$scope.tableRows = tableRows;
-			// must apply scope here to update the tableRows before selecting the row
-			$scope.$apply();
-            $scope.gridApi.selection.selectRow($scope.tableRows[selectedRowIndex]);
+		var selectRow = function (info) {
+			updateEntityChildren(info.entity, info.rows, info.expand);
 			fixTooltips();
 		}
 
-		$scope.getTableHeight = function() {
-	       return {height: ($scope.tableRows.length * 30) + "px"};
-	    };
 	    var titleFromAlt = function (alt) {
 			if (alt && alt.length) {
 				var data = angular.fromJson(alt);
@@ -214,261 +576,144 @@ var QDR = (function(QDR) {
 					var title = titleFromAlt(tip.getAttribute('alt'))
 					tip.setAttribute('original-title', title)
 				}
-/*
-				$(tip).tipsy({html: true, className: 'subTip', opacity: 1, title: function () {
-					var alt = this.getAttribute('alt');
-					if (alt && alt.length) {
-						var data = angular.fromJson(alt);
-						var table = "<table class='tiptable'><tbody>";
-						data.forEach (function (row) {
-							table += "<tr>";
-							table += "<td>" + row.node + "</td><td align='right'>" + QDRService.pretty(row.val) + "</td>";
-							table += "</tr>"
-						})
-						table += "</tbody></table>"
-						return table;
-					}
-					return '';
-				} });
-				*/
 			})
 		}
-		$scope.selectedEntity = undefined;
-		for (var i=0; i<$scope.entities.length; ++i) {
-			if ($scope.selectedAction === $scope.entities[i].name) {
-				$scope.selectedEntity = $scope.entities[i].name;
-				break;
-			}
-		}
-		if (!angular.isDefined($scope.selectedEntity)) {
-			$scope.selectedAction = $scope.entities[0].name;
-			$scope.selectedEntity = $scope.entities[0].name;
-		}
-		var savedCharts = angular.fromJson(localStorage['QDRListCharts']);
-		var getCurrentSavedCharts = function () {
-			if (angular.isDefined(savedCharts)) {
-				if (angular.isDefined(savedCharts[$scope.selectedEntity])) {
-					//graphFields = savedCharts[$scope.selectedEntity];
-				}
-			} else {
-				savedCharts = {};
-			}
-		}
-		getCurrentSavedCharts();
-
-		//QDR.log.debug("using entity of " + $scope.selectedEntity);
-		var stop = undefined;
-
-		// The left-hand table that lists the names
-		var gridCols = [
-			{ field: 'name',
-			  displayName: '',
-			  cellTemplate: '<div class="ngCellText"><span ng-cell-text>{{row.entity.name.value}}</span></div>'
-			}
-		];
-		// the table on the left of the page contains the name field for each record that was returned
-		$scope.gridDef = {
-			data: 'tableRows',
-			hideHeader: true,
-			showHeader: false,
-			enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-			enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
-			columnDefs: gridCols,
-			enableColumnResize: true,
-			multiSelect: false,
-			enableRowHeaderSelection: false,
-			noUnselect: true,
-			enableSelectAll: false,
-			enableRowSelection: true,
-			onRegisterApi: function (gridApi) {
-				$scope.gridApi = gridApi;
-				gridApi.selection.on.rowSelectionChanged($scope, function(row) {
-					if (row.isSelected)  {
-						selectedRowIndex = row.rowIndex;
-						$scope.selectedRecordName = row.entity.name.value;
-						var details = [];
-						// for each field in the new row, add a row in the details grid
-						for (var name in row.entity) {
-							details.push( { attributeName: QDRService.humanify(name),
-											attributeValue: QDRService.pretty(row.entity[name].value),
-											type: row.entity[name].type,
-											name: name,
-											rawValue: row.entity[name].value,
-											graph: row.entity[name].graph,
-											title: row.entity[name].title,
-											aggregateValue: QDRService.pretty(row.entity[name].aggregate),
-											aggregateTip: row.entity[name].aggregateTip})
-						}
-						setTimeout(updateDetails, 10, details);
-					}
-				});
-		    }
-		};
 
 		$scope.detailFields = [];
-		updateDetails = function (details) {
-			$scope.detailFields = details;
-			$scope.$apply();
-		}
-
-		$scope.isFieldGraphed = function(rowEntity, aggregate) {
-			var dot = !aggregate ? '.' : '';
-			return QDRChartService.isAttrCharted($scope.selectedNodeId, dot + $scope.selectedEntity, $scope.selectedRecordName, rowEntity.name);
-		}
 
 		$scope.addToGraph = function(rowEntity) {
-			var chart = QDRChartService.registerChart($scope.selectedNodeId, "." + $scope.selectedEntity, $scope.selectedRecordName, rowEntity.name, 1000);
-			doDialog("template-from-script.html", chart);
-			reset();
+			var chart = QDRChartService.registerChart(
+				{nodeId: $scope.selectedNodeId,
+				 entity: "." + $scope.selectedEntity,
+				 name:   $scope.selectedRecordName,
+				 attr:    rowEntity.name,
+				 forceCreate: true});
+			doDialog(chart);
 		}
 
 		$scope.addAllToGraph = function(rowEntity) {
-			var chart = QDRChartService.registerChart($scope.selectedNodeId,
-						$scope.selectedEntity,
-						$scope.selectedRecordName,
-						rowEntity.name,
-						1000,
-						false,
-						true);
-			doDialog("template-from-script.html", chart);
-			reset();
+			var chart = QDRChartService.registerChart({
+				nodeId:     $scope.selectedNodeId,
+				entity:     $scope.selectedEntity,
+				name:       $scope.selectedRecordName,
+				attr:       rowEntity.name,
+				type:       "rate",
+				rateWindow: udateInterval,
+				visibleDuration: 1,
+				forceCreate: true,
+				aggregate:   true});
+			doDialog(chart);
 		}
 
-		var detailCols = [
-			 {
-				 field: 'attributeName',
-				 cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName">{{row.entity.name}}<i ng-if="row.entity.graph" ng-click="grid.appScope.addToGraph(row.entity)" ng-class="{\'active\': grid.appScope.isFieldGraphed(row.entity, false), \'icon-bar-chart\': row.entity.graph == true }"></i></div>',
-				 displayName: 'Attribute'
-			 },
-			 {
-				 field: 'attributeValue',
-				 displayName: 'Value'
-			 }
-		];
-		if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
-			detailCols.push(
-			 {
-				 width: '10%',
-				 field: 'aggregateValue',
-				 displayName: 'Aggregate',
-				 cellTemplate: '<div class="hastip" alt="{{row.entity.aggregateTip}}">{{row.entity.aggregateValue}}<i ng-if="row.entity.graph" ng-click="grid.appScope.addAllToGraph(row.entity)" ng-class="{\'active\': grid.appScope.isFieldGraphed(row.entity, true), \'icon-bar-chart\': row.entity.graph == true }"></i></div>',
-				 cellClass: 'aggregate'
-			 }
-			)
+		$scope.detailCols = [];
+		var aggregateColumn = function () {
+			if ((aggregateEntities.indexOf($scope.selectedEntity) > -1 && $scope.detailCols.length != 3) ||
+				(aggregateEntities.indexOf($scope.selectedEntity) == -1 && $scope.detailCols.length != 2)) {
+				// column defs have to be reassigned and not spliced, so no push/pop
+				 $scope.detailCols = [
+				 {
+					 field: 'attributeName',
+					 displayName: 'Attribute',
+					 cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName">{{row.entity[col.field]}}<i ng-if="row.entity.graph" ng-click="addToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>'
+				 },
+				 {
+					 field: 'attributeValue',
+					 displayName: 'Value',
+					 cellTemplate: '<div class="ngCellText" ng-class="{\'changed\': row.entity.changed == 1}"><span>{{row.getProperty(col.field)}}</span></div>'
+				 }
+				 ]
+				if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
+					$scope.detailCols.push(
+					 {
+						 width: '10%',
+						 field: 'aggregateValue',
+						 displayName: 'Aggregate',
+						 cellTemplate: '<div class="hastip" alt="{{row.entity.aggregateTip}}"><span ng-class="{\'changed\': row.entity.changed == 1}">{{row.entity[col.field]}}</span><i ng-if="row.entity.graph" ng-click="addAllToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>',
+						 cellClass: 'aggregate'
+					 }
+					)
+				}
+			}
+			if ($scope.selectedRecordName === "")
+				$scope.detailCols = [];
 		}
 
 		// the table on the right of the page contains a row for each field in the selected record in the table on the left
 		$scope.details = {
 			data: 'detailFields',
-			columnDefs: detailCols,
-			enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
-			enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
+			columnDefs: "detailCols",
 			enableColumnResize: true,
 			multiSelect: false,
-			enableRowHeaderSelection: false,
-			noUnselect: true,
-			enableSelectAll: false,
-			enableRowSelection: true
+			beforeSelectionChange: function() {
+				  return false;
+			}
 		};
-
-		updateTableData($scope.selectedEntity);
-		stop = setInterval(updateTableData, 5000, $scope.selectedEntity);
-		$scope.getDetailsTableHeight = function() {
-	       return {height: ($scope.detailFields.length * 30) + "px"};
-	    };
-
 		$scope.$on("$destroy", function( event ) {
 			//QDR.log.debug("scope destroyed for qdrList");
-			reset();
-			if (angular.isDefined(stop)) {
-				clearInterval(stop);
-				stop = undefined;
-			};
+			stopUpdating();
 		});
 
-		var reset = function () {
-			if ($scope.context) {
-				$scope.context.stop();
-				$scope.context = null;
+		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);
+				//QDR.log.debug(context.message.application_properties.statusDescription)
+			} else {
+				var note = entity + " " + $filter('Pascalcase')($scope.currentMode.op) + "d"
+				Core.notification('success', note);
+				$scope.selectMode($scope.modes[0]);
+				restartUpdate();
 			}
-		};
+		}
+		$scope.ok = function () {
+			var attributes = {}
+			$scope.detailFields.forEach( function (field) {
+				var value = field.rawValue;
+				if (field.input === 'input') {
+					if (field.type === 'text' || field.type === 'disabled')
+						value = field.attributeValue;
+				} else if (field.input === 'select') {
+					value = field.selected;
+				} else if (field.input === 'boolean') {
+					value = field.rawValue
+				}
+				if (value === "")
+					value = undefined;
 
-		function doDialog(template, chart) {
+				if ((value && value != field['default']) || field.required || (field.name === 'role')) {
+					if (field.name !== 'identity')
+						attributes[field.name] = value
+				}
+			})
+			QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, gotMethodResponse)
+		}
+		$scope.remove = function () {
+			var attributes = {type: $scope.selectedEntity, name: $scope.selectedRecordName}
+			QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, gotMethodResponse)
+		}
 
-		    var modalInstance = $uibModal.open({
-		      animation: true,
-		      templateUrl: template,
-		      controller: 'QDR.ListChartController',
+		function doDialog(chart) {
+		    var d = $dialog.dialog({
+		      backdrop: true,
+		      keyboard: true,
+		      backdropClick: true,
+		      templateUrl: QDR.templatePath + 'tmplListChart.html',
+		      controller: "QDR.ListChartController",
 		      resolve: {
-		        chart: function () {
-		          return chart;
-		        }
-		      }
+                 chart: function() {
+                   return chart
+                 },
+                 nodeName: function () {
+                    return $scope.selectedNode
+                 }
+              }
 		    });
-		};
-
-	}]);
 
+		    d.open().then(function(result) { console.log("d.open().then"); });
 
-	QDR.module.controller('QDR.ListChartController', function ($scope, $uibModalInstance, $location, QDRChartService, chart) {
-
-	    $scope.chart = chart;
-		$scope.dialogSvgChart = null;
-		$scope.updateTimer = null;
-		$scope.svgDivId = "dialogChart";    // the div id for the svg chart
-
-		$scope.showChartsPage = function () {
-			cleanup();
-		    $uibModalInstance.close();
-			$location.path("/charts");
-		};
-
-		$scope.addChartsPage = function () {
-			QDRChartService.addDashboard($scope.chart);
-		};
-
-		$scope.delChartsPage = function () {
-			QDRChartService.delDashboard($scope.chart);
 		};
+	}]);
 
-		$scope.isOnChartsPage = function () {
-			return $scope.chart.dashboard;
-		}
-
-		var showChart = function () {
-			// the chart divs are generated by angular and aren't available immediately
-			var div = angular.element("#" + $scope.svgDivId);
-			if (!div.width()) {
-				setTimeout(showChart, 100);
-				return;
-			}
-			dialogSvgChart = new QDRChartService.AreaChart($scope.chart, $location.$$path);
-			$scope.dialogSvgChart = dialogSvgChart;
-			updateDialogChart();
-		}
-		showChart();
-
-		var updateDialogChart = function () {
-			if ($scope.dialogSvgChart)
-				$scope.dialogSvgChart.tick($scope.svgDivId);
-			$scope.updateTimer = setTimeout(updateDialogChart, 1000);
-		}
-
-		var cleanup = function () {
-			if ($scope.updateTimer) {
-				clearTimeout($scope.updateTimer);
-				$scope.updateTimer = null;
-			}
-			if (!$scope.chart.dashboard)
-				QDRChartService.unRegisterChart(chart);     // remove the chart
-
-		}
-		$scope.ok = function () {
-			cleanup();
-	        $uibModalInstance.close();
-	    };
-
-	});
     return QDR;
 
 } (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrListChart.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrListChart.js b/console/stand-alone/plugin/js/qdrListChart.js
new file mode 100644
index 0000000..e654b19
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrListChart.js
@@ -0,0 +1,109 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+	QDR.module.controller('QDR.ListChartController', function ($scope, dialog, $dialog, $location, QDRChartService, chart, nodeName) {
+	    $scope.chart = chart;
+		$scope.dialogSvgChart = null;
+		var updateTimer = null;
+		$scope.svgDivId = "dialogChart";    // the div id for the svg chart
+
+		$scope.showChartsPage = function () {
+			cleanup();
+			dialog.close(true);
+			$location.path(QDR.pluginRoot + "/charts");
+		};
+
+		$scope.addHChart = function () {
+	        QDRChartService.addHDash($scope.chart);
+			cleanup();
+	        dialog.close(true);
+		}
+
+		$scope.addToDashboardLink = function () {
+			var href = "#/" + QDR.pluginName + "/charts";
+			var size = angular.toJson({
+	                size_x: 2,
+	                size_y: 2
+	              });
+
+			var params = angular.toJson({chid: $scope.chart.id()});
+	        var title = "Dispatch - " + nodeName;
+		    return "/hawtio/#/dashboard/add?tab=dashboard" +
+		          "&href=" + encodeURIComponent(href) +
+		          "&routeParams=" + encodeURIComponent(params) +
+		          "&title=" + encodeURIComponent(title) +
+		          "&size=" + encodeURIComponent(size);
+	    };
+
+
+		$scope.addChartsPage = function () {
+			QDRChartService.addDashboard($scope.chart);
+		};
+
+		$scope.delChartsPage = function () {
+			QDRChartService.delDashboard($scope.chart);
+		};
+
+		$scope.isOnChartsPage = function () {
+			return $scope.chart.dashboard;
+		}
+
+		var showChart = function () {
+			// the chart divs are generated by angular and aren't available immediately
+			var div = angular.element("#" + $scope.svgDivId);
+			if (!div.width()) {
+				setTimeout(showChart, 100);
+				return;
+			}
+			dialogSvgChart = new QDRChartService.AreaChart($scope.chart);
+			$scope.dialogSvgChart = dialogSvgChart;
+			updateDialogChart();
+		}
+		showChart();
+
+		var updateDialogChart = function () {
+			if ($scope.dialogSvgChart)
+				$scope.dialogSvgChart.tick($scope.svgDivId);
+			if (updateTimer)
+				clearTimeout(updateTimer)
+			updateTimer = setTimeout(updateDialogChart, 1000);
+		}
+
+		var cleanup = function () {
+			if (updateTimer) {
+				clearTimeout(updateTimer);
+				updateTimer = null;
+			}
+			if (!$scope.chart.hdash && !$scope.chart.dashboard)
+				QDRChartService.unRegisterChart($scope.chart);     // remove the chart
+
+		}
+		$scope.ok = function () {
+			cleanup();
+			dialog.close(true);
+	    };
+
+	});
+    return QDR;
+
+} (QDR || {}));


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


[09/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
deleted file mode 100644
index aad0cdf..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
+++ /dev/null
@@ -1,1426 +0,0 @@
-/*
-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) {
-
-  /**
-   * @method OverviewController
-   * @param $scope
-   * @param QDRService
-   * @param QDRChartServer
-   * dialogServer
-   * $location
-   *
-   * Controller that handles the QDR overview page
-   */
-	QDR.module.controller("QDR.OverviewController", ['$scope', 'QDRService', '$location', 'localStorage', '$timeout', function($scope, QDRService, $location, localStorage, $timeout) {
-
-		console.log("QDR.OverviewControll started with location of " + $location.path() + " and connection of  " + QDRService.connected);
-		var columnStateKey = 'QDRColumnKey.';
-		var OverviewExpandedKey = "QDROverviewExpanded"
-		var OverviewActivatedKey = "QDROverviewActivated"
-
-		// 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;
-		}
-		$scope.linkFields = []
-		$scope.link = null;
-		var refreshInterval = 5000
-	    $scope.modes = [
-	    	{title: 'Overview', name: 'Overview', right: false}
-	    	];
-
-		$scope.templates =
-		[     { name: 'Routers', url: 'routers.html'},
-		      { name: 'Router', url: 'router.html'},
-              { name: 'Addresses', url: 'addresses.html'},
-		      { name: 'Address', url: 'address.html'},
-              { name: 'Links', url: 'links.html'},
-		      { name: 'Link', url: 'link.html'},
-              { name: 'Connections', url: 'connections.html'},
-		      { name: 'Connection', url: 'connection.html'},
-              { name: 'Logs', url: 'logs.html'},
-              { name: 'Log', url: 'log.html'}
-        ];
-		var topLevelChildren = [];
-
-		$scope.allRouterSelected = function (row ) {
-			console.log("row selected" + row)
-		}
-		function afterSelectionChange(rowItem, checkAll) {
-			var nodeId = rowItem.entity.nodeId;
-			$("#overtree").dynatree("getTree").activateKey(nodeId);
-        }
-
-		$scope.allRouterFields = [];
-		$scope.allRouterSelections = [];
-		$scope.allRouters = {
-		    saveKey: 'allRouters',
-			data: 'allRouterFields',
-			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'
-                 }
-            ],
-			enableColumnResize: true,
-			multiSelect: false,
-			selectedItems: $scope.allRouterSelections,
-			afterSelectionChange: function(data) {
-				if (data.selected) {
-					var selItem = $scope.allRouterSelections[0]
-					var nodeId = selItem.nodeId
-					// activate Routers->nodeId in the tree
-					$("#overtree").dynatree("getTree").activateKey(nodeId);
-
-				}
-            }
-		};
-
-		// 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], "routerId")
-							allRouterFields.some( function (connField) {
-								if (routerId === connField.routerId) {
-									result.attributeNames.forEach ( function (attrName) {
-										connField[attrName] = QDRService.valFor(result.attributeNames, result.results[0], attrName)
-									})
-									return true
-								}
-								return false
-							})
-						}
-						$scope.allRouterFields = allRouterFields
-					}, nodeIds[0], false)
-				}
-			}
-			nodeIds.forEach ( function (nodeId, i) {
-				QDRService.getNodeInfo(nodeId, ".connection", ["role"], gotNodeInfo)
-			})
-			loadColState($scope.allRouters)
-			updateRouterTree(nodeIds)
-		}
-
-		$scope.routerFields = []
-        $scope.routerGrid = {
-            saveKey: 'routerGrid',
-            data: 'routerFields',
-            columnDefs: [
-                 {
-                     field: 'attribute',
-                     displayName: 'Attribute',
-                     saveKey: 'routerGrid',
-                     width: '40%'
-                 },
-                 {
-                     field: 'value',
-                     displayName: 'Value',
-                     width: '40%'
-                 }
-            ],
-            enableColumnResize: true,
-            multiSelect: false
-        }
-
-		// get info for a single router
-		var routerInfo = function (node) {
-			$scope.router = node
-
-			$scope.routerFields = [];
-			$scope.allRouterFields.some( function (field) {
-				if (field.routerId === node.data.title) {
-					Object.keys(field).forEach ( function (key) {
-						if (key !== '$$hashKey')
-							$scope.routerFields.push({attribute: key, value: field[key]})
-					})
-					return true
-				}
-			})
-	        loadColState($scope.routerGrid);
-		}
-
-		$scope.addressesData = []
-		$scope.selectedAddresses = []
-		$scope.addressesGrid = {
-			saveKey: 'addressesGrid',
-			data: 'addressesData',
-			columnDefs: [
-                 {
-                     field: 'address',
-                    saveKey: 'addressesGrid',
-                     displayName: 'address'
-                 },
-                 {
-                     field: 'class',
-                     displayName: 'class'
-                 },
-                 {
-                     field: 'phase',
-                     displayName: 'phase',
-                     cellClass: 'grid-align-value'
-                 },
-                 {
-                     field: 'inproc',
-                     displayName: 'in-proc'
-                 },
-                 {
-                     field: 'local',
-                     displayName: 'local',
-                     cellClass: 'grid-align-value'
-                 },
-                 {
-                     field: 'remote',
-                     displayName: 'remote',
-                     cellClass: 'grid-align-value'
-                 },
-                 {
-                     field: 'in',
-                     displayName: 'in',
-                     cellClass: 'grid-align-value'
-                 },
-                 {
-                     field: 'out',
-                     displayName: 'out',
-                     cellClass: 'grid-align-value'
-                 }
-            ],
-			enableColumnResize: true,
-			multiSelect: false,
-			selectedItems: $scope.selectedAddresses,
-			afterSelectionChange: function(data) {
-				if (data.selected) {
-					var selItem = data.entity;
-					var nodeId = selItem.uid
-					$("#overtree").dynatree("getTree").activateKey(nodeId);
-				}
-            }
-		};
-
-		// get info for all addresses
-		var allAddressInfo = function () {
-			var gotAllAddressFields = function ( addressFields ) {
-				// 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"
-		        return "unknown: " + addr[0]
-			}
-
-			var addr_text = function (addr) {
-		        if (!addr)
-		            return "-"
-		        if (addr[0] == 'M')
-		            return addr.substring(2)
-		        else
-		            return addr.substring(1)
-			}
-
-			var addr_phase = function (addr) {
-		        if (!addr)
-		            return "-"
-		        if (addr[0] == 'M')
-		            return addr[1]
-		        return ''
-			}
-
-			var identity_clean = function (identity) {
-		        if (!identity)
-		            return "-"
-		        var pos = identity.indexOf('/')
-		        if (pos >= 0)
-		            return identity.substring(pos + 1)
-		        return identity
-			}
-
-			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 = identity_clean(uid)
-
-					addressFields.push({
-						address: addr_text(identity),
-						'class': 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 ) {
-			$scope.linkFields = linkFields
-			// if we have a selected link
-			if ($scope.link) {
-				// find the selected link in the array of all links
-				var links = $scope.linkFields.filter(function (link) {
-					return link.name === $scope.link.data.fields.name;
-				})
-				if (links.length > 0) {
-					// linkInfo() is the function that is called by dynatree when a link is selected
-					// It is passed a dynatree node. We need to simulate that node type to update the link grid
-					linkInfo({data: {title: links[0].title, fields: links[0]}})
-				}
-			}
-		}
-
-		// get info for a all links
-		$scope.selectedLinks = []
-		$scope.linksGrid = {
-			saveKey: 'linksGrid',
-			data: 'linkFields',
-			columnDefs: [
-                 {
-                     field: 'link',
-                     displayName: 'Link',
-                     groupable:	false,
-                     saveKey: 'linksGrid',
-                     width: '12%'
-                 },
-                 {
-                     field: 'linkType',
-                     displayName: 'Link type',
-                     groupable:	false,
-                     width: '10%'
-                 },
-                 {
-                     field: 'linkDir',
-                     displayName: 'Link dir',
-                     groupable:	false,
-                     width: '8%'
-                 },
-                 {
-                     field: 'adminStatus',
-                     displayName: 'Admin status',
-                     groupable:	false,
-                     width: '10%'
-                 },
-                 {
-                     field: 'operStatus',
-                     displayName: 'Oper status',
-                     groupable:	false,
-                     width: '10%'
-                 },
-                 {
-                     field: 'deliveryCount',
-                     displayName: 'Delivery Count',
-                     groupable:	false,
-                     cellClass: 'grid-align-value',
-                     width: '12%'
-                 },
-                 {
-                     field: 'rate',
-                     displayName: 'Rate',
-                     groupable:	false,
-                     cellClass: 'grid-align-value',
-                     width: '8%'
-                 },
-                 {
-                     field: 'uncounts',
-                     displayName: 'Outstanding',
-                     groupable:	false,
-                     cellClass: 'grid-align-value',
-                     width: '10%'
-                 },
-                 {
-                     field: 'owningAddr',
-                     displayName: 'Address',
-                     groupable:	false,
-                     width: '20%'
-                 }
-            ],
-			enableColumnResize: true,
-			enableColumnReordering: true,
-			showColumnMenu: true,
-			rowTemplate: 'linkRowTemplate.html',
-			// aggregateTemplate: "linkAggTemplate.html",
-	        multiSelect: false,
-			selectedItems: $scope.selectedLinks,
-			afterSelectionChange: function(data) {
-				if (data.selected) {
-					var selItem = data.entity;
-					var nodeId = selItem.uid
-					$("#overtree").dynatree("getTree").activateKey(nodeId);
-				}
-            }
-		};
-
-
-		$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) {
-                    grid.columnDefs[index].width = col[0];
-                    grid.columnDefs[index].visible = col[1]
-                })
-            }
-        }
-		var allLinkInfo = function () {
-			getAllLinkFields([updateLinkGrid, updateLinkTree])
-	        loadColState($scope.linksGrid);
-		}
-
-		var getAllLinkFields = function (callbacks) {
-			var nodeIds = QDRService.nodeIdList()
-			var linkFields = []
-			var now = Date.now()
-			var rate = function (response, result) {
-				var name = QDRService.valFor(response.attributeNames, result, "linkName").sum
-				var oldname = $scope.linkFields.filter(function (link) {
-					return link.linkName === name
-				})
-				if (oldname.length > 0) {
-					var elapsed = (now - oldname[0].timestamp) / 1000;
-					var delivered = QDRService.valFor(response.attributeNames, result, "deliveryCount").sum - 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
-				}
-			}
-			QDRService.getMultipleNodeInfo(nodeIds, "router.link", [], 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 uncounts = function () {
-						var und = QDRService.valFor(response.attributeNames, result, "undeliveredCount").sum
-						var uns = QDRService.valFor(response.attributeNames, result, "unsettledCount").sum
-						return und + uns
-					}
-					var nameIndex = response.attributeNames.indexOf('name')
-					var linkName = function () {
-						var details = result[nameIndex].detail
-						var names = []
-						details.forEach( function (detail) {
-							if (detail.node.endsWith(':'))
-								names.push(detail.node.slice(0, -1))
-							else
-								names.push(detail.node)
-						})
-						//var namestr = names.join('-')
-						var namestr = names.length > 0 ? names[0] : ""
-						return namestr + ':' + QDRService.valFor(response.attributeNames, result, "identity").sum
-					}
-					var fixAddress = function () {
-						var owningAddr = QDRService.valFor(response.attributeNames, result, "owningAddr").sum || ""
-						/*
-						     - "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]
-								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 + ")"
-									}
-								}
-							}
-						}
-						return address || owningAddr;
-					}
-
-					var adminStatus = QDRService.valFor(response.attributeNames, result, "adminStatus").sum
-					var operStatus = QDRService.valFor(response.attributeNames, result, "operStatus").sum
-					var linkName = linkName()
-					var linkType = QDRService.valFor(response.attributeNames, result, "linkType").sum
-					if ($scope.currentLinkFilter === "" || $scope.currentLinkFilter === linkType) {
-						linkFields.push({
-							link:       linkName,
-							title:      linkName,
-							uncounts:   uncounts(),
-							operStatus: operStatus,
-							adminStatus:adminStatus,
-							owningAddr: fixAddress(),
-							deliveryCount:prettySum("deliveryCount") + " ",
-							rawDeliveryCount: QDRService.valFor(response.attributeNames, result, "deliveryCount").sum,
-							name: QDRService.valFor(response.attributeNames, result, "name").sum,
-							linkName: QDRService.valFor(response.attributeNames, result, "linkName").sum,
-							capacity: QDRService.valFor(response.attributeNames, result, "capacity").sum,
-							connectionId: QDRService.valFor(response.attributeNames, result, "connectionId").sum,
-							linkDir: QDRService.valFor(response.attributeNames, result, "linkDir").sum,
-							linkType: linkType,
-							peer: QDRService.valFor(response.attributeNames, result, "peer").sum,
-							type: QDRService.valFor(response.attributeNames, result, "type").sum,
-							undeliveredCount: QDRService.valFor(response.attributeNames, result, "undeliveredCount").sum,
-							unsettledCount: QDRService.valFor(response.attributeNames, result, "unsettledCount").sum,
-							uid:     linkName,
-							timestamp: now,
-							rate: rate(response, result)
-						})
-					}
-				})
-				callbacks.forEach( function (cb) {
-					cb(linkFields)
-				})
-			}, nodeIds[0])
-		}
-
-		$scope.allConnectionFields = []
-		$scope.allConnectionSelections = [];
-		$scope.allConnectionGrid = {
-			saveKey: 'allConnGrid',
-            data: 'allConnectionFields',
-            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'
-                 }
-            ],
-            enableColumnResize: true,
-            multiSelect: false,
-            selectedItems: $scope.allConnectionSelections,
-            afterSelectionChange: function(data) {
-                if (data.selected) {
-                    var selItem = $scope.allConnectionSelections[0]
-                    var nodeId = selItem.host
-                    // activate Routers->nodeId in the tree
-                    $("#overtree").dynatree("getTree").activateKey(nodeId);
-                }
-            }
-        };
-		// get info for a all connections
-		var allConnectionInfo = function () {
-			var nodeIds = QDRService.nodeIdList()
-			updateConnectionTree(nodeIds)
-			$scope.allConnectionFields = [];;
-            var connectionNodes = $("#overtree").dynatree("getTree").getNodeByKey('Connections').getChildren()
-			connectionNodes.forEach( function (connection) {
-				$scope.allConnectionFields.push(connection.data.fields)
-			})
-	        loadColState($scope.allConnectionGrid);
-		}
-
-		$scope.addressFields = []
-		$scope.addressGrid = {
-			saveKey: 'addGrid',
-			data: 'addressFields',
-			columnDefs: [
-                 {
-                     field: 'attribute',
-                     displayName: 'Attribute',
-                     saveKey: 'addGrid',
-                     width: '40%'
-                 },
-                 {
-                     field: 'value',
-                     displayName: 'Value',
-                     width: '40%'
-                 }
-            ],
-			enableColumnResize: true,
-			multiSelect: false
-		}
-
-		// get info for a single address
-		var addressInfo = function (address) {
-			if (!address)
-				return;
-			$scope.address = address
-			$scope.addressFields = [];
-
-			var fields = Object.keys(address.data.fields)
-			fields.forEach( function (field) {
-				if (field != "title" && field != "uid")
-					$scope.addressFields.push({attribute: field, value: address.data.fields[field]})
-			})
-	        loadColState($scope.addressGrid);
-		}
-
-		$scope.singleLinkFields = []
-		$scope.linkGrid = {
-		    saveKey: 'linkGrid',
-			data: 'singleLinkFields',
-			columnDefs: [
-                 {
-                    field: 'attribute',
-                    displayName: 'Attribute',
-                     width: '40%'
-                 },
-                 {
-                    field: 'value',
-                    displayName: 'Value',
-                    width: '40%'
-                 }
-            ],
-			enableColumnResize: true,
-			multiSelect: false
-		}
-
-		// 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.data.fields)
-			var excludeFields = ["title", "uid", "uncounts", "rawDeliveryCount", "timestamp"]
-			fields.forEach( function (field) {
-				if (excludeFields.indexOf(field) == -1)
-					$scope.singleLinkFields.push({attribute: field, value: link.data.fields[field]})
-			})
-	        loadColState($scope.linkGrid);
-		}
-
-		// get info for a single connection
-		$scope.connectionModes = [{
-	        content: '<a><i class="icon-list"></i> Attriutes</a>',
-			id: 'attributes',
-			title: "View connection attributes",
-	        isValid: function () { return true; }
-	    },
-	    {
-	        content: '<a><i class="icon-link"></i> Links</a>',
-	        id: 'links',
-	        title: "Show links",
-	        isValid: function () { return true }
-	    }
-	    ];
-        $scope.currentMode = $scope.connectionModes[0];
-		$scope.isModeSelected = function (mode) {
-			return mode === $scope.currentMode;
-		}
-		$scope.connectionLinks = [];
-		var updateConnectionLinks = function () {
-			var n = $scope.connection.data.fields
-			var key = n.routerId
-			var nodeInfo = QDRService.topology.nodeInfo();
-			var links = nodeInfo[key]['.router.link'];
-			var linkTypeIndex = links.attributeNames.indexOf('linkType');
-			var connectionIdIndex = links.attributeNames.indexOf('connectionId');
-			$scope.connectionLinks = [];
-			links.results.forEach( function (link) {
-				if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.identity) {
-					var l = {};
-					l.owningAddr = QDRService.valFor(links.attributeNames, link, 'owningAddr');
-					l.dir = QDRService.valFor(links.attributeNames, link, 'linkDir');
-					if (l.owningAddr && l.owningAddr.length > 2)
-						if (l.owningAddr[0] === 'M')
-							l.owningAddr = l.owningAddr.substr(2)
-						else
-							l.owningAddr = l.owningAddr.substr(1)
-
-					l.adminStatus = QDRService.valFor(links.attributeNames, link, 'adminStatus');
-					l.operStatus = QDRService.valFor(links.attributeNames, link, 'operStatus');
-					l.identity = QDRService.valFor(links.attributeNames, link, 'identity')
-					l.connectionId = QDRService.valFor(links.attributeNames, link, 'connectionId')
-/*
-						l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
-						l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
-						l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
-*/
-					// ---------------------------------------------
-					// TODO: remove this fake quiescing/reviving logic when the routers do the work
-					if ($scope.quiesceState.linkStates[l.identity])
-						l.adminStatus = $scope.quiesceState.linkStates[l.identity];
-					if ($scope.quiesceState.operStates[l.identity])
-						l.operStatus = $scope.quiesceState.operStates[l.identity];
-					if ($scope.quiesceState.state == 'quiescing') {
-						if (l.adminStatus === 'enabled') {
-							var chance = Math.floor(Math.random() * 2);
-							if (chance == 1) {
-								l.adminStatus = 'disabled';
-								$scope.quiesceState.linkStates[l.identity] = 'disabled';
-								$scope.quiesceState.operStates[l.identity] = 'idle';
-							}
-						}
-					}
-					if ($scope.quiesceState.state == 'reviving') {
-						if (l.adminStatus === 'disabled') {
-							var chance = Math.floor(Math.random() * 2);
-							if (chance == 1) {
-								l.adminStatus = 'enabled';
-								$scope.quiesceState.linkStates[l.identity] = 'enabled';
-								$scope.quiesceState.operStates[l.identity] = 'up';
-							}
-						}
-					}
-					if ($scope.quiesceState.linkStates[l.identity] === 'disabled') {
-						l.unsettledCount = 0;
-						l.undeliveredCount = 0;
-						l.operState = 'idle'
-						$scope.quiesceState.operStates[l.identity] = l.operState
-					} else {
-						l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
-						l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
-						l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
-						l.operState = 'up'
-						$scope.quiesceState.operStates[l.identity] = l.operState
-					}
-					// ---------------------------------------------
-
-					$scope.connectionLinks.push(l)
-				}
-			})
-			$scope.connectionLinksGrid.updateState()
-		}
-
-		$scope.selectMode = function (mode) {
-			$scope.currentMode = mode;
-			if (mode.id === 'links') {
-		        QDRService.addUpdatedAction("connectionLinks", updateConnectionLinks)
-				updateConnectionLinks();
-			} else {
-				QDRService.delUpdatedAction("connectionLinks");
-			}
-		}
-		$scope.isValid = function (mode) {
-			return mode.isValid()
-		}
-		$scope.quiesceState = {
-			state: 'enabled',
-			buttonText: 'Quiesce',
-			buttonDisabled: false,
-			linkStates: {},
-			operStates: {}
-		}
-		$scope.quiesceAllClicked = function () {
-			var state = $scope.quiesceState.state;
-			if (state === 'enabled') {
-				// start quiescing all links
-				$scope.quiesceState.state = 'quiescing';
-			} else if (state === 'quiesced') {
-				// start reviving all links
-				$scope.quiesceState.state = 'reviving';
-			}
-			$scope.connectionLinksGrid.updateState();
-		}
-		$scope.quiesceClass = function (row) {
-			var stateClassMap = {
-				enabled: 'btn-primary',
-				quiescing: 'btn-warning',
-				reviving: 'btn-warning',
-				quiesced: 'btn-danger'
-			}
-			return stateClassMap[$scope.quiesceState.state];
-		}
-		$scope.quiesceLinkClass = function (row) {
-			var stateClassMap = {
-				enabled: 'btn-primary',
-				disabled: 'btn-danger'
-			}
-			return stateClassMap[row.entity.adminStatus]
-		}
-		$scope.quiesceLink = function (row) {
-			var state = row.entity.adminStatus === 'enabled' ? 'disabled' : 'enabled';
-			var operState = state === 'enabled' ? 'up' : 'idle';
-			$scope.quiesceState.linkStates[row.entity.identity] = state;
-			$scope.quiesceState.operStates[row.entity.identity] = operState;
-		}
-		$scope.quiesceLinkDisabled = function (row) {
-			return false;
-		}
-		$scope.quiesceLinkText = function (row) {
-			return row.entity.adminStatus === 'disabled' ? "Revive" : "Quiesce";
-		}
-		$scope.quiesceHide = function () {
-			return $scope.connectionLinks.length == 0;
-		}
-		$scope.connectionLinksGrid = {
-		    saveKey: 'connLinksGrid',
-
-			data: 'connectionLinks',
-			updateState: function () {
-				var state = $scope.quiesceState.state;
-
-				// count enabled and disabled links for this connection
-				var enabled = 0, disabled = 0;
-				$scope.connectionLinks.forEach ( function (link) {
-					if (link.adminStatus === 'enabled')
-						++enabled;
-					if (link.adminStatus === 'disabled')
-						++disabled;
-				})
-
-				var linkCount = $scope.connectionLinks.length;
-				if (linkCount == 0) {
-					$scope.quiesceState.buttonText = 'Quiesce';
-					$scope.quiesceState.buttonDisabled = false;
-					$scope.quiesceState.state = 'enabled'
-					return;
-				}
-				// if state is quiescing and any links are enabled, button should say 'Quiescing' and be disabled
-				if (state === 'quiescing' && (enabled > 0)) {
-					$scope.quiesceState.buttonText = 'Quiescing';
-					$scope.quiesceState.buttonDisabled = true;
-				} else
-				// if state is enabled and all links are disabled, button should say Revive and be enabled. set state to quisced
-				// if state is quiescing and all links are disabled, button should say 'Revive' and be enabled. set state to quiesced
-				if ((state === 'quiescing' || state === 'enabled') && (disabled === linkCount)) {
-					$scope.quiesceState.buttonText = 'Revive';
-					$scope.quiesceState.buttonDisabled = false;
-					$scope.quiesceState.state = 'quiesced'
-				} else
-				// if state is reviving and any links are disabled, button should say 'Reviving' and be disabled
-				if (state === 'reviving' && (disabled > 0)) {
-					$scope.quiesceState.buttonText = 'Reviving';
-					$scope.quiesceState.buttonDisabled = true;
-				} else
-				// if state is reviving or quiesced and all links are enabled, button should say 'Quiesce' and be enabled. set state to enabled
-				if ((state === 'reviving' || state === 'quiesced') && (enabled === linkCount)) {
-					$scope.quiesceState.buttonText = 'Quiesce';
-					$scope.quiesceState.buttonDisabled = false;
-					$scope.quiesceState.state = 'enabled'
-				}
-
-				if ($scope.quiesceState.state === 'quiesced') {
-					d3.selectAll('.external.connection.dynatree-active')
-						.classed('quiesced', true)
-				} else {
-					d3.selectAll('.external.connection.dynatree-active.quiesced')
-						.classed('quiesced', false)
-				}
-			},
-            columnDefs: [
-			{
-				field: 'adminStatus',
-                cellTemplate: "titleCellTemplate.html",
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-			    saveKey: 'connLinksGrid',
-				displayName: 'Admin state'
-			},
-			{
-				field: 'operStatus',
-                cellTemplate: "titleCellTemplate.html",
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-				displayName: 'Oper state'
-			},
-			{
-				field: 'dir',
-                cellTemplate: "titleCellTemplate.html",
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-				displayName: 'dir'
-			},
-			{
-				field: 'owningAddr',
-                cellTemplate: "titleCellTemplate.html",
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-				displayName: 'Address'
-			},
-			{
-				field: 'deliveryCount',
-				displayName: 'Delivered',
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-				cellClass: 'grid-values'
-
-			},
-			{
-				field: 'undeliveredCount',
-				displayName: 'Undelivered',
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-				cellClass: 'grid-values'
-			},
-			{
-				field: 'unsettledCount',
-				displayName: 'Unsettled',
-				headerCellTemplate: 'titleHeaderCellTemplate.html',
-				cellClass: 'grid-values'
-			}/*,
-			{
-				cellClass: 'gridCellButton',
-				cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>'
-			}*/
-			]
-		}
-
-		$scope.connectionFields = []
-		$scope.connectionGrid = {
-		    saveKey: 'connGrid',
-			data: 'connectionFields',
-			columnDefs: [
-                 {
-                     field: 'attribute',
-                     displayName: 'Attribute',
-                    saveKey: 'connGrid',
-                     width: '40%'
-                 },
-                 {
-                     field: 'value',
-                     displayName: 'Value',
-                     width: '40%'
-                 }
-            ],
-			enableColumnResize: true,
-			multiSelect: false
-		}
-
-		var connectionInfo = function (connection) {
-			$scope.connection = connection
-
-			$scope.connectionFields = [];
-			var fields = Object.keys(connection.data.fields)
-			fields.forEach( function (field) {
-				$scope.connectionFields.push({attribute: field, value: connection.data.fields[field]})
-			})
-			$scope.selectMode($scope.currentMode)
-	        loadColState($scope.connectionGrid);
-		}
-
-		// get info for a all logs
-		var allLogInfo = function () {
-		}
-
-		// get info for a single log
-		var logInfo = function (node) {
-			$scope.log = node
-		}
-
-		var getExpandedList = function () {
-			var list = [];
-			var tree = $("#overtree").dynatree("getRoot");
-			if (tree.length !== 0) {
-				tree.visit(function(node){
-	                if (node.isExpanded()) {
-	                    list.push(node.data.parent)
-	                }
-	            });
-			}
-			return list;
-		}
-
-		var updateExpanded = function () {
-			var tree = $("#overtree").dynatree("getRoot");
-			if (tree.length !== 0) {
-				tree.visit(function(node){
-	                if (node.isExpanded() || node.isActive()) {
-	                    node.data.info(node)
-	                }
-	            });
-			}
-		}
-
-		// 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)
-		}
-
-		// activated is called each time a tree node is clicked
-		// based on which node is clicked, load the correct data grid template and start getting the data
-		var activated = function (node) {
-			//QDR.log.debug("node activated: " + node.data.title)
-			var type = node.data.type;
-			saveExpanded()
-			saveActivated(node.data.key)
-
-			var template = $scope.templates.filter( function (tpl) {
-				return tpl.name == type;
-			})
-			$scope.template = template[0];
-			// the nodes info function will fetch the grids data
-			if (node.data.info) {
-				$timeout(function () {node.data.info(node)})
-			}
-
-		}
-        $scope.template = {url: ''};
-
-		if (!QDRService.connected) {
-			QDRService.redirectWhenConnected("overview")
-			return;
-		}
-
-		// we are currently connected. setup a handler to get notified if we are ever disconnected
-		QDRService.addDisconnectAction( function () {
-			QDRService.redirectWhenConnected("overview")
-			$scope.$apply();
-		})
-
-	/* --------------------------------------------------
-	 *
-     * setup the tree on the left
-     *
-     * -------------------------------------------------
-     */
-		// utility function called by each top level tree node when it needs to populate its child nodes
-		var updateLeaves = function (leaves, parentKey, parentFolder, worker) {
-			var scrollTree = $('.qdr-overview.pane.left .pane-viewport')
-			var scrollTop = scrollTree.scrollTop();
-			var tree = $("#overtree").dynatree("getTree")
-			var parentNode = tree.count ? tree.getNodeByKey(parentKey) : null;
-			// if we were called after the tree is created, replace the existing nodes
-			var activeNode;
-			if (parentNode) {
-				activeNode = tree.getActiveNode();
-				parentNode.removeChildren();
-			}
-			leaves.forEach( function (leaf) {
-				var childFolder = worker(leaf)
-				if (parentNode)
-					parentNode.addChild(childFolder)
-				else
-					parentFolder.children.push(childFolder)
-			})
-			scrollTree.scrollTop(scrollTop)
-			if (activeNode) {
-				var newNode = tree.getNodeByKey(activeNode.data.key)
-				newNode.activateSilently()
-			}
-		}
-
-		// get saved tree state
-        var lastKey = loadActivatedNode();
-		var expandedNodeList = loadExpandedNodeList();
-
-		// create a routers tree branch
-		var routers = new Folder("Routers")
-		routers.type = "Routers"
-		routers.info = allRouterInfo
-		routers.activate = lastKey === 'Routers'
-		routers.expand = (expandedNodeList.indexOf("Routers") > -1)
-		routers.clickFolderMode = 1
-		routers.key = "Routers"
-		routers.parent = "Routers"
-	    routers.addClass = "routers"
-		topLevelChildren.push(routers)
-		// called when the list of routers changes
-		var updateRouterTree = function (nodes) {
-			var worker = function (node) {
-				var name = QDRService.nameFromId(node)
-				var router = new Folder(name)
-				router.type = "Router"
-				router.info = routerInfo
-				router.nodeId = node
-				router.key = node
-				router.addClass = "router"
-				router.parent = "Routers"
-				router.activate = lastKey === node
-				return router;
-			}
-			updateLeaves(nodes, "Routers", routers, worker)
-		}
-		updateRouterTree(QDRService.nodeIdList());
-
-		// create an addresses tree branch
-		var addresses = new Folder("Addresses")
-		addresses.type = "Addresses"
-		addresses.info = allAddressInfo
-		addresses.activate = lastKey === 'Addresses'
-		addresses.expand = (expandedNodeList.indexOf("Addresses") > -1)
-		addresses.clickFolderMode = 1
-		addresses.key = "Addresses"
-		addresses.parent = "Addresses"
-	    addresses.addClass = "addresses"
-		topLevelChildren.push(addresses)
-		var updateAddressTree = function (addressFields) {
-			var worker = function (address) {
-				var a = new Folder(address.title)
-				a.info = addressInfo
-				a.key = address.uid
-				a.fields = address
-				a.type = "Address"
-				a.addClass = "address"
-				a.activate = lastKey === address.uid
-				a.parent = "Addresses"
-				return a;
-			}
-			updateLeaves(addressFields, "Addresses", addresses, worker)
-		}
-		allAddressInfo();
-
-		$scope.setLinkFilter = function (cur) {
-			// filter out non-matching links from the tree and the grid
-			getAllLinkFields([updateLinkGrid, updateLinkTree])
-		}
-		$scope.filterClose = function () {
-			var filter = $('#linkFilter')
-			filter.hide();
-		}
-		$scope.currentLinkFilter = "endpoint"
-		var filterHtml = "<button type='button' class='btn btn-secondary btn-filter'><span class='filter-icon'><i class='icon-filter'> Filter</span></button>";
-		var links = new Folder("Links " + filterHtml)
-		links.type = "Links"
-		links.info = allLinkInfo
-		links.activate = lastKey === 'Links'
-		links.expand = (expandedNodeList.indexOf("Links") > -1)
-		links.clickFolderMode = 1
-		links.key = "Links"
-		links.parent = "Links"
-	    links.addClass = "links"
-		topLevelChildren.push(links)
-
-		// called both before the tree is created and whenever a background update is done
-		var updateLinkTree = function (linkFields) {
-			var worker = function (link) {
-				var l = new Folder(link.title)
-				l.info = linkInfo
-				l.key = link.uid
-				l.fields = link
-				l.type = "Link"
-				l.parent = "Links"
-				l.activate = lastKey === link.uid
-				l.addClass = "link"
-				return l;
-			}
-			linkFields.sort ( function (a,b) { return a.link < b.link ? -1 : a.link > b.link ? 1 : 0})
-			updateLeaves(linkFields, "Links", links, worker)
-		}
-		allLinkInfo();
-
-		var connections = new Folder("Connections")
-		connections.type = "Connections"
-		connections.info = allConnectionInfo
-		connections.activate = lastKey === 'Connections'
-		connections.expand = (expandedNodeList.indexOf("Connections") > -1)
-		connections.clickFolderMode = 1
-		connections.key = "Connections"
-		connections.parent = "Connections"
-	    connections.addClass = "connections"
-		topLevelChildren.push(connections)
-
-		updateConnectionTree = function (nodes) {
-			var connectionsObj = {}
-			var expected = nodes.length;
-			var connreceived = 0;
-			nodes.forEach( function (nodeId) {
-				QDRService.getNodeInfo(nodeId, ".connection", [], 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")
-						connectionsObj[host] = {}
-						response.attributeNames.forEach( function (attribute, i) {
-							connectionsObj[host][attribute] = result[i]
-						})
-						connectionsObj[host].security = sec
-						connectionsObj[host].authentication = auth
-						connectionsObj[host].routerId = nodeName
-
-					})
-					++connreceived;
-					if (connreceived == expected) {
-						var worker = function (connection) {
-							var c = new Folder(connection)
-							c.type = "Connection"
-							c.info = connectionInfo
-							c.key = connection
-							c.fields = connectionsObj[connection]
-							c.tooltip = connectionsObj[connection].role === "inter-router" ? "inter-router connection" : "external connection"
-							c.addClass = c.tooltip
-							c.parent = "Connections"
-							c.activate = lastKey === connection
-							return c
-						}
-						var allConnections = Object.keys(connectionsObj).sort()
-						updateLeaves(allConnections, "Connections", connections, worker)
-					}
-				})
-			})
-		}
-		updateConnectionTree(QDRService.nodeIdList())
-
-		var logsreceived = 0;
-		var logObj = {}
-		var logs = new Folder("Logs")
-		logs.type = "Logs"
-		logs.info = allLogInfo
-		logs.activate = lastKey === 'Logs'
-		logs.expand = (expandedNodeList.indexOf("Logs") > -1)
-		logs.clickFolderMode = 1
-		logs.key = "Logs"
-		logs.parent = "Logs"
-		//topLevelChildren.push(logs)
-		var nodeIds = QDRService.nodeIdList()
-		var expected = nodeIds.length;
-		nodeIds.forEach( function (nodeId) {
-			QDRService.getNodeInfo(nodeId, ".log", ["name"], function (nodeName, entity, response) {
-				response.results.forEach( function (result) {
-					logObj[result[0]] = 1    // use object to collapse duplicates
-				})
-				++logsreceived;
-				if (logsreceived == expected) {
-					var allLogs = Object.keys(logObj).sort()
-					allLogs.forEach(function (log) {
-						var l = new Folder(log)
-						l.type = "Log"
-						l.info = logInfo
-						l.key = log
-						l.parent = "Logs"
-						l.activate = lastKey === log
-						logs.children.push(l)
-					})
-					$('#overtree').dynatree({
-						onActivate: activated,
-						onExpand: saveExpanded,
-						onClick: function (n, e) {
-							if (e.target.className.indexOf('-filter') > -1) {
-								//QDR.log.debug("overtree on click called")
-								e.preventDefault();
-								e.stopPropagation()
-								var filter = $('#linkFilter')
-								var treeLink = $('span.links')
-								filter.css({
-                                              top: treeLink.offset().top + treeLink.height(),
-                                              left: treeLink.offset().left,
-                                              zIndex:5000
-                                            });
-								filter.toggle()
-								$("#overtree").dynatree("getTree").getNodeByKey('Links').activate()
-								return false;
-							}
-						},
-						selectMode: 1,
-						activeVisible: false,
-						children: topLevelChildren
-					})
-					// simulate a click on the previous active node
-					var active = $("#overtree").dynatree("getActiveNode");
-					if (!active) {
-						active = $("#overtree").dynatree("getTree").getNodeByKey("Routers")
-					}
-					activated(active);
-
-					// populate the data for each expanded node
-					updateExpanded();
-					QDRService.addUpdatedAction( "overview", function () {
-						$timeout(updateExpanded);
-					})
-					// update the node list
-					QDRService.startUpdating()
-
-			        loadColState($scope.allRouters);
-			        loadColState($scope.routerGrid);
-			        loadColState($scope.addressesGrid);
-			        loadColState($scope.addressGrid);
-			        loadColState($scope.linksGrid);
-			        loadColState($scope.linkGrid);
-			        loadColState($scope.allConnectionGrid);
-			        loadColState($scope.connectionGrid);
-
-				}
-			})
-		})
-
-        $scope.$on("$destroy", function( event ) {
-			QDRService.stopUpdating()
-			QDRService.delUpdatedAction("overview")
-        });
-
-    }]);
-
-  return QDR;
-
-}(QDR || {}));
-
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
new file mode 120000
index 0000000..de6a7a1
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrOverview.js
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js b/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js
deleted file mode 100644
index 656b159..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-/**
- * @module QDR
- */
-var QDR = (function (QDR) {
-
-    QDR.module.controller("QDR.SchemaController", ['$scope', '$location', 'QDRService', function($scope, $location, QDRService) {
-		if (!QDRService.connected) {
-			QDRService.redirectWhenConnected("schema")
-			return;
-		}
-		// we are currently connected. setup a handler to get notified if we are ever disconnected
-		QDRService.addDisconnectAction( function () {
-			QDRService.redirectWhenConnected("schema")
-			$scope.$apply();
-		})
-
-        var keys2kids = function (tree, obj) {
-
-			if (obj === Object(obj)) {
-				tree.children = []
-	            for (var key in obj) {
-	                var kid = {title: key}
-	                if (obj[key] === Object(obj[key])) {
-	                    kid.isFolder = true
-	                    keys2kids(kid, obj[key])
-	                } else {
-						kid.title += (': ' + JSON.stringify(obj[key],null,2))
-	                }
-	                tree.children.push(kid)
-	            }
-			}
-        }
-
-		var tree = []
-		for (var key in QDRService.schema) {
-			var kid = {title: key}
-			var val = QDRService.schema[key]
-			if (val === Object(val))
-				keys2kids(kid, val)
-			else
-				kid.title += (': ' + JSON.stringify(val,null,2))
-
-			tree.push(kid);
-		}
-        $('#schema').dynatree({
-			minExpandLevel: 2,
-            children: tree
-        })
-
-
-    }]);
-
-    return QDR;
-}(QDR || {}));
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js b/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js
new file mode 120000
index 0000000..5010e61
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrSchema.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrSchema.js
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrService.js b/console/hawtio/src/main/webapp/plugin/js/qdrService.js
deleted file mode 100644
index 09c3b92..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrService.js
+++ /dev/null
@@ -1,888 +0,0 @@
-/*
-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) {
-
-  // The QDR service handles the connection to
-  // the server in the background
-  QDR.module.factory("QDRService", ['$rootScope', '$http', '$timeout', '$resource', '$location', function($rootScope, $http, $timeout, $resource, $location) {
-    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 = [];
-		if ($location.path().startsWith(QDR.pluginRoot)) {
-	        $timeout( function () {
-
-	            var searchObject = $location.search();
-	            var goto = "overview";
-	            if (searchObject.org) {
-	                goto = searchObject.org;
-	            }
-	            $location.search('org', null)
-	            $location.path(QDR.pluginRoot +"/" + goto);
-	        })
-		}
-
-      },
-      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 (action in self.updatedActions) {
-			try {
-                self.updatedActions[action].apply();
-            } catch (e) {
-                delete self.updatedActions[action]
-            }
-        }
-      },
-	redirectWhenConnected: function (org) {
-		$location.path("/" + QDR.pluginName + "/connect")
-		$location.search('org', org);
-	},
-
-      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() {
-        //QDR.log.debug("*************QDR init proton called ************");
-      },
-      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) {
-          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) {
-			// TODO: use connection properties when available
-			if (d.properties.console_identifier == 'Dispatch console')
-				return true;
-			// until connection properties can difinitively identify consoles:
-			var connid = d.connectionId;
-			if (connid && d.nodeType === 'normal') {
-				// find all the endpoint links for this router that have this connid
-				var linkInfo = self.topology.nodeInfo()[d.key]['.router.link']
-				var outs = 0, ins = 0;
-				var outaddr, inaddr;
-				linkInfo.results.forEach( function (link) {
-					if (self.valFor(linkInfo.attributeNames, link, 'connectionId') == connid &&
-						self.valFor(linkInfo.attributeNames, link, 'linkType') == 'endpoint') {
-						if (self.valFor(linkInfo.attributeNames, link, 'linkDir') == 'in') {
-							++ins;
-							inaddr = self.valFor(linkInfo.attributeNames, link, 'owningAddr')
-						}
-						if (self.valFor(linkInfo.attributeNames, link, 'linkDir') == 'out') {
-							++outs;
-							outaddr = self.valFor(linkInfo.attributeNames, link, 'owningAddr')
-						}
-						return true;
-					}
-				})
-				// consoles have 1 out link with an address that starts with Ltemp. and
-				// 1 in link with no address
-				if (outs == 1 && ins == 1 &&
-					inaddr == null && outaddr.startsWith('Ltemp.')) {
-					return true;
-				}
-			}
-			return false;
-		},
-
-      /*
-       * 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) {
-/*
-            for (var o in obj) {
-                QDR.log.debug("cleaning up");
-                console.dump(obj[o]);
-                if (isNaN(parseInt(o)))
-                    this.cleanUp(obj[o]);
-            }
-*/
-            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, callback) {
-		var ret;
-		self.correlator.request(
-			ret = self._sendMethod(nodeId, entity, attrs, operation)
-		).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) {
-		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
-			}
-			var ent = self.schema.entityTypes[entity];
-			var fullyQualifiedType = ent.fullyQualifiedType;
-			if (fullyQualifiedType) {
-				application_properties.type = fullyQualifiedType
-			}
-			else
-				application_properties.type = entity;
-			if (attrs.name)
-				application_properties.name = attrs.name;
-			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(options) {
-        self.options = options;
-        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();
-			}
-
-			QDR.log.debug("****** calling rhea.connect ********")
-			var connection;
-			try {
-                connection = self.rhea.connect({
-                    connection_details:ws('ws://' + baseAddress, ["binary", "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 || {}));
-
-(function() {
-    console.dump = function(object) {
-        if (window.JSON && window.JSON.stringify)
-            QDR.log.info(JSON.stringify(object,undefined,2));
-        else
-            console.log(object);
-    };
-})();
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrService.js b/console/hawtio/src/main/webapp/plugin/js/qdrService.js
new file mode 120000
index 0000000..2d2a672
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrService.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrService.js
\ 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


[05/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/tmplListChart.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplListChart.html b/console/stand-alone/plugin/html/tmplListChart.html
new file mode 100644
index 0000000..e52a39a
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplListChart.html
@@ -0,0 +1,18 @@
+<!--
+    This is the template for the graph dialog that is displayed.
+-->
+<div class="modal-header">
+    <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
+</div>
+<div class="modal-body">
+    <div id="{{svgDivId}}" class="d3Chart"></div>
+</div>
+<div class="modal-footer">
+        <span ng-hide="isOnChartsPage()">
+            <button class="btn btn-success" type="button" ng-click="addChartsPage()"><i class="icon-bar-chart"></i> Add</button> this chart to the Charts page.
+        </span>
+        <span ng-show="isOnChartsPage()">
+            <button class="btn btn-danger" type="button" ng-click="delChartsPage()">Remove</button> this chart from the <button class="btn btn-success" type="button" ng-click="showChartsPage()"><i class="icon-bar-chart"></i> Charts</button> page.
+        </span>
+    <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/tmplListTree.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplListTree.html b/console/stand-alone/plugin/html/tmplListTree.html
new file mode 100644
index 0000000..9aa8be5
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplListTree.html
@@ -0,0 +1,17 @@
+<div class="qdr-attributes pane left" position="left" width="300">
+    <div class="pane-wrapper">
+        <div class="pane-header-wrapper">
+        </div>
+        <div class="pane-viewport">
+            <div class="pane-content">
+                <div class="treeContainer">
+                    <div class="tree-header"><select ng-options="node as node.name for node in nodes" ng-model="currentNode" ng-change="selectNode(currentNode)"></select></div>
+                    <div id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div>
+                    <div ng-init="treeReady()"></div>
+                </div>
+            </div>
+        </div>
+        <div class="pane-bar"></div>
+    </div>
+</div>
+<ng-include src="'listGrid.html'"></ng-include>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/html/tmplOverviewTree.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplOverviewTree.html b/console/stand-alone/plugin/html/tmplOverviewTree.html
new file mode 100644
index 0000000..4346b9c
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplOverviewTree.html
@@ -0,0 +1,15 @@
+<div class="qdr-overview pane left" position="left" width="300">
+    <div class="pane-wrapper">
+        <div class="pane-header-wrapper">
+        </div>
+        <div class="pane-viewport">
+            <div class="pane-content">
+                <div class="treeContainer ng-scope">
+                    <div id="overtree"></div>
+                </div>
+            </div>
+        </div>
+        <div class="pane-bar" ng-mousedown="startMoving($event)" ng-click="toggle()"></div>
+    </div>
+</div>
+<ng-include src="'overviewGrid.html'"></ng-include>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/dispatchPlugin.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/dispatchPlugin.js b/console/stand-alone/plugin/js/dispatchPlugin.js
new file mode 100644
index 0000000..e8078e9
--- /dev/null
+++ b/console/stand-alone/plugin/js/dispatchPlugin.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.
+*/
+/**
+ * @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;
+
+  /**
+   * @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(QDR.pluginName, ['ngResource', 'ngGrid', 'ui.bootstrap', 'ui.slider'/*, 'minicolors' */]);
+
+  // set up the routing for this plugin
+  QDR.module.config(function($routeProvider) {
+    $routeProvider
+      .when('/', {
+        templateUrl: QDR.templatePath + 'qdrConnect.html'
+        })
+      .when('/overview', {
+          templateUrl: QDR.templatePath + 'qdrOverview.html'
+        })
+      .when('/topology', {
+          templateUrl: QDR.templatePath + 'qdrTopology.html'
+        })
+      .when('/list', {
+          templateUrl: QDR.templatePath + 'qdrList.html'
+        })
+      .when('/schema', {
+          templateUrl: QDR.templatePath + 'qdrSchema.html'
+        })
+      .when('/charts', {
+          templateUrl: QDR.templatePath + 'qdrCharts.html'
+        })
+      .when('/connect', {
+          templateUrl: QDR.templatePath + 'qdrConnect.html'
+        })
+      .otherwise({
+          templateUrl: QDR.templatePath + 'qdrConnect.html'
+        })
+  });
+
+	QDR.module.config(function ($compileProvider) {
+		var cur = $compileProvider.urlSanitizationWhitelist();
+		$compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|blob):/);
+		cur = $compileProvider.urlSanitizationWhitelist();
+	})
+
+	QDR.module.filter('to_trusted', ['$sce', function($sce){
+          return function(text) {
+              return $sce.trustAsHtml(text);
+          };
+    }]);
+
+	QDR.module.filter('humanify', 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", "QDRService", "QDRChartService",  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)
+				})
+			})
+			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 {
+            $timeout(function () {
+				$location.path('/connect')
+				$location.search('org', org)
+            })
+        }
+
+        $rootScope.$on('$routeChangeSuccess', function() {
+            var path = $location.path();
+			if (path !== "/connect") {
+	            localStorage[QDR.LAST_LOCATION] = path;
+			}
+        });
+
+	}]);
+
+	QDR.module.controller ("QDR.MainController", ['$scope', '$location', function ($scope, $location) {
+		QDR.log.debug("started QDR.MainController with location.url: " + $location.url());
+		QDR.log.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.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;
+})();

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/navbar.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/navbar.js b/console/stand-alone/plugin/js/navbar.js
index ebda4d4..337be52 100644
--- a/console/stand-alone/plugin/js/navbar.js
+++ b/console/stand-alone/plugin/js/navbar.js
@@ -34,39 +34,38 @@ var QDR = (function (QDR) {
         content: '<i class="icon-cogs"></i> Connect',
         title: "Connect to a router",
         isValid: function () { return true; },
-        href: "#connect"
+        href: "#" + QDR.pluginRoot + "/connect"
     },
     {
-        content: '<i class="fa fa-home"></i> Overview',
+        content: '<i class="icon-home"></i> Overview',
         title: "View router overview",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/overview"
+        href: "#" + QDR.pluginRoot + "/overview"
       },
     {
-        content: '<i class="icon-star-empty"></i> Topology',
-        title: "View router network topology",
+        content: '<i class="icon-list "></i> Entities',
+        title: "View the attributes of the router entities",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/topology"
+        href: "#" + QDR.pluginRoot + "/list"
       },
     {
-        content: '<i class="icon-list "></i> List',
-        title: "View router nodes as a list",
+        content: '<i class="icon-star-empty"></i> Topology',
+        title: "View router network topology",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/list"
+        href: "#" + QDR.pluginRoot + "/topology"
       },
     {
         content: '<i class="icon-bar-chart"></i> Charts',
         title: "View charts",
-        isValid: function (QDRService, $location) { return QDRService.isConnected(); },
+        isValid: function (QDRService, $location) { return QDRService.isConnected() && QDR.isStandalone; },
         href: "#/charts"
     },
     {
         content: '<i class="icon-align-left"></i> Schema',
         title: "View dispatch schema",
         isValid: function (QDRService) { return QDRService.isConnected(); },
-        href: "#/schema",
+        href: "#" + QDR.pluginRoot + "/schema",
         right: true
-
       }
   ];
   /**
@@ -78,28 +77,16 @@ var QDR = (function (QDR) {
    * The controller for this plugin's navigation bar
    *
    */
-  QDR.module.controller("QDR.NavBarController", ['$scope', '$sce', 'QDRService', 'QDRChartService', '$location', function($scope, $sce, QDRService, QDRChartService, $location) {
-
-	QDR.log.debug("navbar started with location.url: " + $location.url());
-	QDR.log.debug("navbar started with window.location.pathname : " + window.location.pathname);
-
-    if ($location.path().startsWith("/topology")
-    && !QDRService.isConnected()) {
-      $location.path("/connect");
-    }
-
-    if ($location.path().startsWith("/connect")
-    && QDRService.isConnected()) {
-      $location.path("/topology");
-    }
-
+  QDR.module.controller("QDR.NavBarController", ['$scope', 'QDRService', 'QDRChartService', '$routeParams', '$location', function($scope, QDRService, QDRChartService, $routeParams, $location) {
     $scope.breadcrumbs = QDR.breadcrumbs;
-
     $scope.isValid = function(link) {
       return link.isValid(QDRService, $location);
     };
 
     $scope.isActive = function(href) {
+		// highlight the connect tab if we are on the root page
+		if (($location.path() === QDR.pluginRoot) && (href.split("#")[1] === QDR.pluginRoot + "/connect"))
+			return true
         return href.split("#")[1] == $location.path();
     };
 
@@ -112,8 +99,30 @@ var QDR = (function (QDR) {
             return QDRChartService.charts.some(function (c) { return c.dashboard });
         }
     }
+
+	$scope.isDashboardable = function () {
+		return  ($location.path().indexOf("schema") < 0 && $location.path().indexOf("connect") < 0);
+	}
+
+	$scope.addToDashboardLink = function () {
+		var href = "#" + $location.path();
+		var size = angular.toJson({
+                size_x: 2,
+                size_y: 2
+              });
+
+        var routeParams = angular.toJson($routeParams);
+        var title = "Dispatch Router";
+	    return "/hawtio/#/dashboard/add?tab=dashboard" +
+	          "&href=" + encodeURIComponent(href) +
+	          "&routeParams=" + encodeURIComponent(routeParams) +
+	          "&title=" + encodeURIComponent(title) +
+	          "&size=" + encodeURIComponent(size);
+    };
+
   }]);
 
+
   return QDR;
 
 } (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrChartService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrChartService.js b/console/stand-alone/plugin/js/qdrChartService.js
index efc607d..47cb47d 100644
--- a/console/stand-alone/plugin/js/qdrChartService.js
+++ b/console/stand-alone/plugin/js/qdrChartService.js
@@ -22,8 +22,8 @@ under the License.
 var QDR = (function(QDR) {
 
     // The QDR chart service handles periodic gathering data for charts and displaying the charts
-    QDR.module.factory("QDRChartService", ['$rootScope', 'QDRService', '$http', '$resource',
-    function($rootScope, QDRService, $http, $resource) {
+    QDR.module.factory("QDRChartService", ['$rootScope', 'QDRService', '$http', '$resource', '$location',
+    function($rootScope, QDRService, $http, $resource, $location) {
 
         var instance = 0;   // counter for chart instances
         var bases = [];
@@ -56,26 +56,32 @@ var QDR = (function(QDR) {
 
         // Object that represents a visible chart
         // There can be multiple of these per ChartBase (eg. one rate  and one value chart)
-        function Chart(name, attr, request) {
+        function Chart(opts, request) { //name, attr, cinstance, request) {
 
-            var base = findBase(name, attr, request);
+            var base = findBase(opts.name, opts.attr, request);
             if (!base) {
-                base = new ChartBase(name, attr, request);
+                base = new ChartBase(opts.name, opts.attr, request);
                 bases.push(base);
             }
             this.base = base;
-            this.instance = instance++;
+            this.instance = angular.isDefined(opts.instance) ? opts.instance : ++instance;
             this.dashboard = false;     // is this chart on the dashboard page
-            this.type = "value";        // value or rate
-            this.rateWindow = 1000;     // calculate the rate of change over this time interval. higher == smother graph
-            this.areaColor = "#c0e0ff"; // the chart's area color when not an empty string
-            this.lineColor = "#4682b4"; // the chart's line color when not an empty string
-            this.visibleDuration = 10;  // number of minutes of data to show (<= base.duration)
+            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 key = this.request().nodeId + this.request().entity + this.name() + this.attr() + "_" + this.instance;
+                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, '')
             }
@@ -87,6 +93,9 @@ var QDR = (function(QDR) {
                 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 (_) {
@@ -109,6 +118,11 @@ var QDR = (function(QDR) {
                 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 = _;
@@ -145,8 +159,16 @@ var QDR = (function(QDR) {
                 return this;
             }
             this.copy = function () {
-                var chart = self.registerChart(this.nodeId(), this.entity(),
-                            this.name(), this.attr(), this.interval(), true, this.base.request.aggregate);
+                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;
@@ -167,17 +189,17 @@ var QDR = (function(QDR) {
         }
 
         // Object that represents the management request to fetch and store data for multiple charts
-        function ChartRequest(nodeId, entity, name, attr, interval, aggregate) {
-            this.duration = 10;         // number of minutes to keep the data
-            this.nodeId = nodeId;       // eg amqp:/_topo/0/QDR.A/$management
-            this.entity = entity;       // eg .router.address
+        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 = 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.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 = interval;   // number of milliseconds between updates to data
-            this.setTimeoutHandle = null;   // used to cancel the next request
+            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) {
@@ -193,7 +215,7 @@ var QDR = (function(QDR) {
 					this.datum[name][attr] = [];
 				}
 			}
-			this.addAttrName(name, attr)
+			this.addAttrName(opts.name, opts.attr)
 
             this.copyProps = function (o) {
                 o.nodeId = this.nodeId;
@@ -232,6 +254,8 @@ var QDR = (function(QDR) {
 				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
@@ -251,12 +275,15 @@ var QDR = (function(QDR) {
 				return ret;
 			},
 
-            findCharts: function (name, attr, nodeId, entity) {
+            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() == name &&
-                            chart.attr() == attr &&
-                            chart.nodeId() == nodeId &&
-                            chart.entity() == entity)
+                    return (chart.name() == opts.name &&
+                            chart.attr() == opts.attr &&
+                            chart.nodeId() == opts.nodeId &&
+                            chart.entity() == opts.entity &&
+                            chart.hdash == opts.hdash)
                 });
             },
 
@@ -290,22 +317,24 @@ var QDR = (function(QDR) {
                 }
             },
 
-            registerChart: function (nodeId, entity, name, attr, interval, forceCreate, aggregate) {
-                var request = self.findChartRequest(nodeId, entity, aggregate);
+            registerChart: function (opts) { //nodeId, entity, name, attr, interval, instance, forceCreate, aggregate, hdash) {
+                var request = self.findChartRequest(opts.nodeId, opts.entity, opts.aggregate);
                 if (request) {
                     // add any new attr or name to the list
-                    request.addAttrName(name, attr)
+                    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: " + nodeId + " " + entity);
-                    request = new ChartRequest(nodeId, entity, name, attr, interval, aggregate);
+                    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(name, attr, nodeId, entity);
+                var charts = self.findCharts(opts); //name, attr, nodeId, entity, hdash);
                 var chart;
-                if (charts.length == 0 || forceCreate) {
-                    chart = new Chart(name, attr, request);
+                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];
@@ -317,6 +346,16 @@ var QDR = (function(QDR) {
             // if all attrs are gone for this request, remove the request
             unRegisterChart: function (chart) {
                 // 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)) {
@@ -337,6 +376,8 @@ var QDR = (function(QDR) {
                         }
                     }
                 }
+				self.saveCharts();
+
             },
 
             stopCollecting: function (request) {
@@ -350,16 +391,29 @@ var QDR = (function(QDR) {
                 // 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) {
+            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) {
-	                QDR.log.debug("got chart results for " + nodeId + " " + entity);
-	                // records an array that has data for all names
+					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;
@@ -397,27 +451,52 @@ var QDR = (function(QDR) {
 				}
 				if (request.aggregate) {
 					var nodeList = QDRService.nodeIdList()
-	                QDR.log.debug("sent multiple chart request for " + nodeList + " " + request.entity, + " " + attrs + " " + request.nodeId);
 					QDRService.getMultipleNodeInfo(nodeList, request.entity, attrs, saveResponse, request.nodeId);
 				} else {
-	                QDR.log.debug("sent chart request for " + request.nodeId + " " + request.entity + " " + attrs);
                     QDRService.getNodeInfo(request.nodeId, request.entity, attrs, saveResponse);
 				}
-                // it is now safe to send another request
+                // it is now safe to schedule another request
+                if (once)
+                    return;
                 request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request)
             },
 
             numCharts: function () {
-                return self.charts.length;
+                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, attr, nodeId, entity);
+                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();
+				/*
+				if (!chart.hdash) {
+                    var dashChart = self.registerChart(chart.nodeId(), chart.entity(),
+                            chart.name(), chart.attr(), chart.interval(), true, chart.aggregate(), true);
+					dashChart.dashboard = true;
+					dashChart.hdash = false;
+					chart.dashboard = false;
+					chart.hdash = true;
+					self.saveCharts();
+				}
+				*/
+			},
+			delHDash: function (chart) {
+				chart.hdash = false;
+				self.saveCharts();
+			},
             addDashboard: function (chart) {
                 chart.dashboard = true;
                 self.saveCharts();
@@ -434,7 +513,7 @@ var QDR = (function(QDR) {
                 self.charts.forEach(function (chart) {
                     var minChart = {};
                     // don't save chart unless it is on the dashboard
-                    if (chart.dashboard) {
+                    if (chart.dashboard || chart.hdash) {
                         chart.copyProps(minChart);
                         minCharts.push(minChart);
                     }
@@ -469,8 +548,53 @@ var QDR = (function(QDR) {
                     })
                 }
             },
+            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, url) {
+            AreaChart: function (chart) {
                 if (!chart)
                     return;
 
@@ -478,11 +602,7 @@ var QDR = (function(QDR) {
 				var stacked = chart.request().aggregate;
                 this.chart = chart; // reference to underlying chart
                 this.svgchart = null;
-                if (url)
-                    url = "/dispatch" + url;
-                else
-                    url = "";
-                this.url = url;
+                this.url = $location.absUrl();
 
                 // callback function. called by svgchart when binding data
                 // the variable 'this' refers to the svg and not the AreaChart,
@@ -600,7 +720,8 @@ var QDR = (function(QDR) {
                                 this.chart.name(),
                                 QDRService.nameFromId(this.chart.nodeId()),
                                 this.chart.entity(),
-                                stacked)
+                                stacked,
+                                this.chart.visibleDuration)
                             .tooltipGenerator(tooltipGenerator);
 
                     }
@@ -619,7 +740,7 @@ var QDR = (function(QDR) {
                 }
             },
 
-            timeSeriesStackedChart: function (id, width, height, attrName, name, node, entity, stacked) {
+            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>);
@@ -633,18 +754,19 @@ var QDR = (function(QDR) {
 							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
+							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
+					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 \u2218 xValue.
+                // 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 \u2218 yValue.
+                // 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")
@@ -672,7 +794,6 @@ var QDR = (function(QDR) {
 			          .y(function (d) { return d.value; });
 
 			    var area = d3.svg.area()
-
 				if (stacked) {
 			        area.interpolate("cardinal")
 			          .x(function (d) { return x(d.date); })
@@ -691,7 +812,22 @@ var QDR = (function(QDR) {
 			        .append("g")
 			          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
 
-			    var clip = svg.append("defs").append("svg:clipPath")
+				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")
@@ -699,7 +835,7 @@ var QDR = (function(QDR) {
 			        .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")
@@ -741,8 +877,11 @@ var QDR = (function(QDR) {
 				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) {
 
@@ -769,12 +908,15 @@ var QDR = (function(QDR) {
 					}
 
                     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]});
-                    var max = d3.max(data, function(d) {return d[1]});
+                    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);
@@ -839,7 +981,6 @@ var QDR = (function(QDR) {
 
 						series.exit().remove()
 
-						//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); })
@@ -847,23 +988,30 @@ var QDR = (function(QDR) {
 	                    var series = container.selectAll(".series")
 	                      .data([data], function(d) { return d; })
 
-	                      series.enter().append("g")
-	                        .append("path")
+						var g = series.enter().append("g")
+							.attr("class", "series")
+
+	                    g.append("path")
 	                        .attr("class", "area")
-						  series.enter().append("path")
-	                        .attr("class", "line")
+							.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()
 
-                        // Update the area path.
-                        container.select(".area").data([data])
-                          .attr("d", area.y0(y.range()[0]))
-						  .style("fill", attrs.areaColor);
+						sv.selectAll("stop")
+					        .attr("stop-color", attrs.areaColor)
 
-                        //Update the line path.
-                        container.select(".line").data([data])
-                          .attr("d", line)
-						  .style("stroke", attrs.lineColor)
 					}
                     // Update the x-axis.
                     svg.select(".x.axis")

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrCharts.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrCharts.js b/console/stand-alone/plugin/js/qdrCharts.js
index e484b07..8bddca5 100644
--- a/console/stand-alone/plugin/js/qdrCharts.js
+++ b/console/stand-alone/plugin/js/qdrCharts.js
@@ -19,44 +19,58 @@ under the License.
 /**
  * @module QDR
  */
-/**
- * @module QDR
- */
 var QDR = (function (QDR) {
 
   /**
    * @method ChartsController
-   * @param $scope
-   * @param QDRServer
-   * @param QDRChartServer
    *
    * Controller that handles the QDR charts page
    */
-  QDR.module.controller("QDR.ChartsController", ['$scope', 'QDRService', 'QDRChartService', '$uibModal', '$location',
-  function($scope, QDRService, QDRChartService, $uibModal, $location) {
-    var updateTimer = null;
+  QDR.module.controller("QDR.ChartsController", function($scope, QDRService, QDRChartService, $dialog, $location, $routeParams) {
+
+	var updateTimer = null;
+
+	if (!QDRService.connected) {
+		// we are not connected. we probably got here from a bookmark or manual page reload
+		QDRService.redirectWhenConnected("charts");
+		return;
+	}
+	// we are currently connected. setup a handler to get notified if we are ever disconnected
+	QDRService.addDisconnectAction( function () {
+		QDRService.redirectWhenConnected("charts")
+		$scope.$apply();
+	})
 
-    QDR.log.debug("started Charts controller");
-    if (!angular.isDefined(QDRService.schema))
-        return;
 
     $scope.svgCharts = [];
     // create an svg object for each chart
     QDRChartService.charts.filter(function (chart) {return chart.dashboard}).forEach(function (chart) {
-        var svgChart = new QDRChartService.AreaChart(chart, $location.$$path)
+        var svgChart = new QDRChartService.AreaChart(chart)
         svgChart.zoomed = false;
         $scope.svgCharts.push(svgChart);
     })
 
-    // redraw the charts every second
+    // redraw the chart every update period
+	// this is a $scope function because it is called from the dialog
     var updateCharts = function () {
         $scope.svgCharts.forEach(function (svgChart) {
             svgChart.tick(svgChart.chart.id()); // on this page we are using the chart.id() as the div id in which to render the chart
         })
-        updateHandle = setTimeout(updateCharts, 1100);
+		var updateRate = localStorage['updateRate'] ?  localStorage['updateRate'] : 5000;
+		if (updateTimer) {
+			clearTimeout(updateTimer)
+		}
+        updateTimer = setTimeout(updateCharts, updateRate);
     }
+
+        // called by ng-init in the html when the page is loaded
 	$scope.chartsLoaded = function () {
-		setTimeout(updateCharts, 0);
+	    $scope.svgCharts.forEach(function (svgChart) {
+                QDRChartService.sendChartRequest(svgChart.chart.request(), true);
+            })
+            if (updateTimer)
+                clearTimeout(updateTimer)
+	    setTimeout(updateCharts, 0);
 	}
 
 	$scope.zoomChart = function (chart) {
@@ -88,12 +102,12 @@ var QDR = (function (QDR) {
     // called from dialog when we want to clone the dialog chart
     // the chart argument here is a QDRChartService chart
     $scope.addChart = function (chart) {
-        $scope.svgCharts.push(new QDRChartService.AreaChart(chart, $location.$$path));
+        $scope.svgCharts.push(new QDRChartService.AreaChart(chart));
     };
 
     $scope.$on("$destroy", function( event ) {
         if (updateTimer) {
-            cancelTimer(updateTimer);
+            clearTimeout(updateTimer);
             updateTimer = null;
         }
         for (var i=$scope.svgCharts.length-1; i>=0; --i) {
@@ -103,28 +117,33 @@ var QDR = (function (QDR) {
 
     function doDialog(template, chart) {
 
-	    var modalInstance = $uibModal.open({
-	      animation: true,
-	      templateUrl: template,
-	      controller: 'QDR.ChartDialogController',
-	      resolve: {
-	        chart: function () {
-	          return chart;
-	        },
-	        dashboard: function () {
-	            return $scope;
-	        }
-	      }
-	    });
+	    $dialog.dialog({
+			backdrop: true,
+			keyboard: true,
+			backdropClick: true,
+			templateUrl: template,
+			controller: "QDR.ChartDialogController",
+			resolve: {
+				chart: function() {
+					return chart;
+				},
+				updateTick: function () {
+					return updateCharts;
+				},
+				dashboard: function () {
+       	            return $scope;
+       	        }
+			}
+	    }).open();
     };
 
-  }]);
+  });
 
-  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, $uibModalInstance, $rootScope, chart, dashboard) {
+  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, dialog, $rootScope, chart, updateTick, dashboard) {
         var dialogSvgChart = null;
         $scope.svgDivId = "dialogChart";    // the div id for the svg chart
 
-		$scope.updateTimer = null;
+	var updateTimer = null;
         $scope.chart = chart;  // the underlying chart object from the dashboard
         $scope.dialogChart = $scope.chart.copy(); // the chart object for this dialog
         $scope.userTitle = $scope.chart.title();
@@ -132,51 +151,85 @@ var QDR = (function (QDR) {
         $scope.$watch('userTitle', function(newValue, oldValue) {
             if (newValue !== oldValue) {
                 $scope.dialogChart.title(newValue);
+                dialogSvgChart.tick($scope.svgDivId);
             }
         })
+		$scope.$watch("dialogChart.areaColor", function (newValue, oldValue) {
+			if (newValue !== oldValue) {
+				if (dialogSvgChart)
+                    dialogSvgChart.tick($scope.svgDivId);
+			}
+		})
+		$scope.$watch("dialogChart.lineColor", function (newValue, oldValue) {
+			if (newValue !== oldValue) {
+				if (dialogSvgChart)
+                    dialogSvgChart.tick($scope.svgDivId);
+			}
+		})
+		$scope.$watch("dialogChart.type", function (newValue, oldValue) {
+			if (newValue !== oldValue) {
+				if (dialogSvgChart)
+                    dialogSvgChart.tick($scope.svgDivId);
+			}
+		})
+
         // the stored rateWindow is in milliseconds, but the slider is in seconds
         $scope.rateWindow = $scope.chart.rateWindow / 1000;
 
 		var cleanup = function () {
-			if ($scope.updateTimer) {
-				clearTimeout($scope.updateTimer);
-				$scope.updateTimer = null;
+			if (updateTimer) {
+				clearTimeout(updateTimer);
+				updateTimer = null;
 			}
 			QDRChartService.unRegisterChart($scope.dialogChart);     // remove the chart
 		}
 		$scope.okClick = function () {
-			cleanup();
-	        $uibModalInstance.close();
+		    cleanup();
+	            dialog.close();
 	    };
 
-        // initialize the rateWindow slider
-        $scope.slider = {
-            'options': {
-                min: 1,
-                max: 10,
-                step: 1,
-                tick: true,
-                stop: function (event, ui) {
-                    $scope.dialogChart.rateWindow = ui.value * 1000;
-                    if (dialogSvgChart)
-                        dialogSvgChart.tick($scope.svgDivId);
-                }
-            }
-		};
-
-        $scope.visibleDuration =
-        $scope.duration = {
-            'options': {
-                min: 1,
-                max: 10,
-                step: 1,
-                tick: true,
-                stop: function (event, ui) {
-                    if (dialogSvgChart)
-                        dialogSvgChart.tick($scope.svgDivId);
-                }
-            }
-		};
+		var initRateSlider = function () {
+			if (document.getElementById('rateSlider')) {
+				$( "#rateSlider" ).slider({
+				      value: $scope.rateWindow,
+				      min: 1,
+				      max: 10,
+				      step: 1,
+				      slide: function( event, ui ) {
+						$scope.rateWindow = ui.value;
+						$scope.dialogChart.rateWindow = ui.value * 1000;
+						$scope.$apply();
+						if (dialogSvgChart)
+							dialogSvgChart.tick($scope.svgDivId);
+				      }
+				});
+
+			} else {
+				setTimeout(initRateSlider, 100)
+			}
+		}
+		initRateSlider();
+
+		var initDurationSlider = function () {
+			if (document.getElementById('durationSlider')) {
+				$( "#durationSlider" ).slider({
+				      value: $scope.dialogChart.visibleDuration,
+				      min: 1,
+				      max: 10,
+				      step: 1,
+				      slide: function( event, ui ) {
+						$scope.visibleDuration = $scope.dialogChart.visibleDuration = ui.value;
+						$scope.$apply();
+						if (dialogSvgChart)
+							dialogSvgChart.tick($scope.svgDivId);
+				      }
+				});
+
+			} else {
+				setTimeout(initDurationSlider, 100)
+			}
+		}
+		initDurationSlider();
 
         // handle the Apply button click
         // update the dashboard chart's properties
@@ -184,10 +237,14 @@ var QDR = (function (QDR) {
             $scope.chart.areaColor = $scope.dialogChart.areaColor;
             $scope.chart.lineColor = $scope.dialogChart.lineColor;
             $scope.chart.type = $scope.dialogChart.type;
-            $scope.chart.rateWindow = $scope.dialogChart.rateWindow;
+            $scope.chart.rateWindow = $scope.rateWindow * 1000;
             $scope.chart.title($scope.dialogChart.title());
             $scope.chart.visibleDuration = $scope.dialogChart.visibleDuration;
             QDRChartService.saveCharts();
+			if (typeof updateTick === "function")
+				updateTick();
+	    if (typeof updateTick === "function")
+		updateTick();
         }
 
         // add a new chart to the dashboard based on the current dialog settings
@@ -206,7 +263,10 @@ var QDR = (function (QDR) {
                 dialogSvgChart.tick($scope.svgDivId);
 
             // draw the chart again in 1 second
-            $scope.updateTimer = setTimeout(updateDialogChart, 1000);
+			var updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 5000;
+			if (updateTimer)
+				clearTimeout(updateTimer);
+            updateTimer = setTimeout(updateDialogChart, updateRate);
         }
 
         var showChart = function () {
@@ -216,7 +276,19 @@ var QDR = (function (QDR) {
                 setTimeout(showChart, 100);
                 return;
             }
-            dialogSvgChart = new QDRChartService.AreaChart($scope.dialogChart, $location.$$path);
+            dialogSvgChart = new QDRChartService.AreaChart($scope.dialogChart);
+			$('input[name=lineColor]').val($scope.dialogChart.lineColor);
+			$('input[name=areaColor]').val($scope.dialogChart.areaColor);
+			$('input[name=areaColor]').on('input', function (e) {
+				$scope.dialogChart.areaColor = $(this).val();
+				updateDialogChart()
+			})
+			$('input[name=lineColor]').on('input', function (e) {
+				$scope.dialogChart.lineColor = $(this).val();
+				updateDialogChart()
+			})
+			if (updateTimer)
+				clearTimeout(updateTimer);
             updateDialogChart();
         }
         showChart();


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


[10/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js b/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js
deleted file mode 100644
index 47cb47d..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js
+++ /dev/null
@@ -1,1111 +0,0 @@
-/*
-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) {
-
-    // The QDR chart service handles periodic gathering data for charts and displaying the charts
-    QDR.module.factory("QDRChartService", ['$rootScope', 'QDRService', '$http', '$resource', '$location',
-    function($rootScope, QDRService, $http, $resource, $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());
-                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();
-            },
-
-			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) {
-                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)
-                            self.saveCharts();
-                    }
-                }
-                if (foundBases == 1) {
-                    var baseIndex = bases.indexOf(chart.base)
-                    bases.splice(baseIndex, 1);
-                }
-            },
-
-            registerChart: function (opts) { //nodeId, entity, name, attr, interval, instance, forceCreate, aggregate, hdash) {
-                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) {
-                // 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);
-                        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);
-                            }
-                        }
-                    }
-                }
-				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();
-				/*
-				if (!chart.hdash) {
-                    var dashChart = self.registerChart(chart.nodeId(), chart.entity(),
-                            chart.name(), chart.attr(), chart.interval(), true, chart.aggregate(), true);
-					dashChart.dashboard = true;
-					dashChart.hdash = false;
-					chart.dashboard = false;
-					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) {
-                    var nodeList = QDRService.nodeList().map( function (node) {
-                        return node.id;
-                    })
-                    charts.forEach(function (chart) {
-                        if (nodeList.indexOf(chart.nodeId) >= 0) {
-	                        if (!chart.interval)
-	                            chart.interval = 1000;
-	                        if (!chart.duration)
-	                            chart.duration = 10;
-	                        if (chart.nodeList)
-	                            chart.aggregate = true;
-	                        var newChart = self.registerChart(chart.nodeId, chart.entity, chart.name, chart.attr, chart.interval, true, chart.aggregate);
-	                        newChart.dashboard = true;  // we only save the dashboard charts
-	                        newChart.type = chart.type;
-	                        newChart.rateWindow = chart.rateWindow;
-	                        newChart.areaColor = chart.areaColor ? chart.areaColor : "#c0e0ff";
-	                        newChart.lineColor = chart.lineColor ? chart.lineColor : "#4682b4";
-	                        newChart.duration(chart.duration);
-	                        newChart.visibleDuration = chart.visibleDuration ? chart.visibleDuration : 10;
-	                        if (chart.userTitle)
-	                            newChart.title(chart.userTitle);
-                        }
-                    })
-                }
-            },
-            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;
-                        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 || {}));
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js b/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js
new file mode 120000
index 0000000..dc5df4c
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrChartService.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrChartService.js
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrCharts.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrCharts.js b/console/hawtio/src/main/webapp/plugin/js/qdrCharts.js
index f37018f..30a9b99 100644
--- a/console/hawtio/src/main/webapp/plugin/js/qdrCharts.js
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrCharts.js
@@ -27,16 +27,21 @@ var QDR = (function (QDR) {
    * Controller that handles displaying a chart on the hawtio dashboard page. Only one chart is displayed per instance of this
    * page
    */
-  QDR.module.controller("QDR.ChartsController", function($scope, QDRService, QDRChartService, $dialog, $location, localStorage, $routeParams) {
+  QDR.module.controller("QDR.ChartsController", function($scope, QDRService, QDRChartService, $dialog, $location, $routeParams) {
 
 	var updateTimer = null;
 
 	if (!QDRService.connected && !$routeParams.chid) {
 		// we are not connected. we probably got here from a bookmark or manual page reload
-		$location.path("/" + QDR.pluginName + "/connect")
-        $location.search('org', "charts");
+		QDRService.redirectWhenConnected("charts");
 		return;
 	}
+	// we are currently connected. setup a handler to get notified if we are ever disconnected
+	QDRService.addDisconnectAction( function () {
+		QDRService.redirectWhenConnected("charts")
+		$scope.$apply();
+	})
+
 
     $scope.svgCharts = [];
     // create an svg object for each chart
@@ -74,7 +79,7 @@ var QDR = (function (QDR) {
 	// the link that the above login prompt will display
 	$scope.loginHref = QDR.pluginName + "/connect";
 
-	// call by ng-init in the html when the page is loaded
+	// called by ng-init in the html when the page is loaded
 	$scope.chartsLoaded = function () {
         $scope.svgCharts.forEach(function (svgChart) {
             QDRChartService.sendChartRequest(svgChart.chart.request(), true);
@@ -169,7 +174,7 @@ var QDR = (function (QDR) {
   });
 
 	// the edit chart properties dialog
-  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, dialog, $rootScope, localStorage, chart, updateTick) {
+  QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, dialog, $rootScope, chart, updateTick) {
 
 		UI.colors[0] = "#cbe7f3"
 		UI.colors[1] = "#058dc7"
@@ -178,7 +183,7 @@ var QDR = (function (QDR) {
         var dialogSvgChart = null;
         $scope.svgDivId = "dialogChart";    // the div id for the svg chart
 
-		var updateTimer = null;
+	var updateTimer = null;
         $scope.chart = chart;  // the underlying chart object from the dashboard
         $scope.dialogChart = $scope.chart.copy(); // the chart object for this dialog
         $scope.userTitle = $scope.chart.title();
@@ -191,8 +196,9 @@ var QDR = (function (QDR) {
         })
 		$scope.$watch("dialogChart.areaColor", function (newValue, oldValue) {
 			if (newValue !== oldValue) {
-				if (dialogSvgChart)
+				if (dialogSvgChart) {
                     dialogSvgChart.tick($scope.svgDivId);
+                }
 			}
 		})
 		$scope.$watch("dialogChart.lineColor", function (newValue, oldValue) {
@@ -219,8 +225,8 @@ var QDR = (function (QDR) {
 			QDRChartService.unRegisterChart($scope.dialogChart);     // remove the chart
 		}
 		$scope.okClick = function () {
-			cleanup();
-	        dialog.close();
+		    cleanup();
+	            dialog.close();
 	    };
 
 		var initRateSlider = function () {
@@ -283,9 +289,9 @@ var QDR = (function (QDR) {
         // update the chart on the popup dialog
         var updateDialogChart = function () {
             // draw the chart using the current data
-            if (dialogSvgChart)
+            if (dialogSvgChart) {
                 dialogSvgChart.tick($scope.svgDivId);
-
+			}
             // draw the chart again in 1 second
 			var updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 5000;
 			if (updateTimer)
@@ -301,6 +307,16 @@ var QDR = (function (QDR) {
                 return;
             }
             dialogSvgChart = new QDRChartService.AreaChart($scope.dialogChart);
+			$('input[name=lineColor]').val($scope.dialogChart.lineColor);
+			$('input[name=areaColor]').val($scope.dialogChart.areaColor);
+			$('input[name=areaColor]').on('input', function (e) {
+				$scope.dialogChart.areaColor = $(this).val();
+                updateDialogChart()
+			})
+			$('input[name=lineColor]').on('input', function (e) {
+				$scope.dialogChart.lineColor = $(this).val();
+                updateDialogChart()
+			})
 			if (updateTimer)
 				clearTimeout(updateTimer);
             updateDialogChart();

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrList.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrList.js b/console/hawtio/src/main/webapp/plugin/js/qdrList.js
deleted file mode 100644
index 7aa808e..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrList.js
+++ /dev/null
@@ -1,811 +0,0 @@
-/*
-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) {
-
-  /**
-   * @method ListController
-   * @param $scope
-   * @param QDRService
-   *
-   * Controller for the main interface
-   */
-	QDR.module.controller("QDR.ListController", ['$scope', '$location', '$dialog', '$filter', 'localStorage', 'QDRService', 'QDRChartService',
-		function ($scope, $location, $dialog, $filter, localStorage, QDRService, QDRChartService) {
-
-		var updateIntervalHandle = undefined;
-		var updateInterval = 5000;
-		var ListExpandedKey = "QDRListExpanded";
-		$scope.details = {};
-
-		$scope.selectedEntity = localStorage['QDRSelectedEntity'] || "address";
-		$scope.selectedNode = localStorage['QDRSelectedNode'];
-		$scope.selectedNodeId = localStorage['QDRSelectedNodeId'];
-		$scope.selectedRecordName = localStorage['QDRSelectedRecordName'];
-		$scope.nodes = []
-		$scope.currentNode = undefined;
-		$scope.modes = [{
-	        content: '<a><i class="icon-list"></i> Attributes</a>',
-			id: 'attributes',
-			op: 'READ',
-			title: "View router attributes",
-	        isValid: function () { return true; }
-	    },
-	    {
-	        content: '<a><i class="icon-edit"></i> Update</a>',
-	        id: 'operations',
-	        op: 'UPDATE',
-	        title: "Update this attribute",
-	        isValid: function () { return $scope.operations.indexOf(this.op) > -1 }
-	    },
-	    {
-	        content: '<a><i class="icon-plus"></i> Create</a>',
-	        id: 'operations',
-	        op: 'CREATE',
-	        title: "Create a new attribute",
-	        isValid: function () { return $scope.operations.indexOf(this.op) > -1 }
-	    },
-	    {
-	        content: '<a><i class="icon-remove"></i> Delete</a>',
-	        id: 'delete',
-	        op: 'DELETE',
-	        title: "Delete",
-	        isValid: function () { return $scope.operations.indexOf(this.op) > -1 }
-	    },
-	    {
-	        content: '<a><i class="icon-chart"></i> Chart</a>',
-	        id: 'charts',
-	        op: 'graph',
-	        title: "Graph attributes",
-	        isValid: function () { return false; return $scope.detailFields.some( function (field ) {
-                                                 					return field.graph;
-                                                 				})}
-	    },
-	    {
-	        content: '<a><i class="icon-eye-open"></i> Fetch</a>',
-	        id: 'log',
-	        op: 'GET-LOG',
-	        title: "Fetch recent log entries",
-	        isValid: function () { return ($scope.selectedEntity === 'log') }
-	    }
-	    ];
-		$scope.operations = []
-        $scope.currentMode = $scope.modes[0];
-		$scope.isModeSelected = function (mode) {
-			return mode === $scope.currentMode;
-		}
-		$scope.selectMode = function (mode) {
-			$scope.currentMode = mode;
-			if (mode.id === 'log') {
-				$scope.logResults = "getting recent log entries...";
-				QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, {}, $scope.currentMode.op, function (nodeName, entity, response, context) {
-					$scope.logResults = response.filter( function (entry) {
-						return entry[0] === $scope.detailsObject.module
-					}).sort( function (a, b) {
-						return b[5] - a[5]
-					}).map( function (entry) {
-						return {
-							type: entry[1],
-							message: entry[2],
-							source: entry[3],
-							line: entry[4],
-							time: Date(entry[5]).toString()
-						}
-					})
-					$scope.$apply();
-				})
-			}
-		}
-		$scope.isValid = function (mode) {
-			return mode.isValid()
-		}
-
-		if (!QDRService.connected) {
-			// we are not connected. we probably got here from a bookmark or manual page reload
-			QDRService.redirectWhenConnected("list");
-			return;
-		}
-		// we are currently connected. setup a handler to get notified if we are ever disconnected
-		QDRService.addDisconnectAction( function () {
-			QDRService.redirectWhenConnected("list")
-			$scope.$apply();
-		})
-
-		$scope.nodes = QDRService.nodeList().sort(function (a, b) { return a.name.toLowerCase() > b.name.toLowerCase()});
-		// unable to get node list? Bail.
-		if ($scope.nodes.length == 0) {
-			$location.path("/" + QDR.pluginName + "/connect")
-			$location.search('org', "list");
-		}
-		if (!angular.isDefined($scope.selectedNode)) {
-			//QDR.log.debug("selectedNode was " + $scope.selectedNode);
-			if ($scope.nodes.length > 0) {
-				$scope.selectedNode = $scope.nodes[0].name;
-				$scope.selectedNodeId = $scope.nodes[0].id;
-				//QDR.log.debug("forcing selectedNode to " + $scope.selectedNode);
-			}
-		}
-		var setCurrentNode = function () {
-			$scope.nodes.some( function (node, i) {
-				if (node.name === $scope.selectedNode) {
-					$scope.currentNode = $scope.nodes[i]
-					return true;
-				}
-			})
-		}
-		setCurrentNode();
-		if ($scope.currentNode == undefined) {
-			if ($scope.nodes.length > 0) {
-				$scope.selectedNode = $scope.nodes[0].name;
-				$scope.selectedNodeId = $scope.nodes[0].id;
-				$scope.currentNode = $scope.nodes[0];
-			}
-		}
-
-		var excludedEntities = ["management", "org.amqp.management", "operationalEntity", "entity", "configurationEntity", "dummy", "console"];
-		var aggregateEntities = ["router.address"];
-		var classOverrides = {
-			"connection": function (row) {
-				return row.role.value;
-			},
-			"router.link": function (row) {
-				return row.linkType.value;
-			}
-		}
-
-		var lookupOperations = function () {
-			var ops = QDRService.schema.entityTypes[$scope.selectedEntity].operations.filter( function (op) { return op !== 'READ'});
-			$scope.operation = ops.length ? ops[0] : "";
-			return ops;
-		}
-
-        var entityTreeChildren = [];
-        var expandedList = angular.fromJson(localStorage[ListExpandedKey]) || [];
-        var sortedEntities = Object.keys(QDRService.schema.entityTypes).sort();
-		sortedEntities.forEach( function (entity) {
-			if (excludedEntities.indexOf(entity) == -1) {
-				if (!angular.isDefined($scope.selectedEntity)) {
-					$scope.selectedEntity = entity;
-					$scope.operations = lookupOperations()
-				}
-				var e = new Folder(entity)
-				e.typeName = "entity"
-                e.key = entity
-				e.expand = (expandedList.indexOf(entity) > -1)
-				var placeHolder = new Folder("loading...")
-				placeHolder.addClass = "loading"
-			    e.children = [placeHolder]
-				entityTreeChildren.push(e)
-			}
-		})
-		$scope.treeReady = function () {
-			$('#entityTree').dynatree({
-				onActivate: onTreeSelected,
-				onExpand: onTreeNodeExpanded,
-				selectMode: 1,
-				activeVisible: false,
-				children: entityTreeChildren
-			})
-			restartUpdate();    // start getting the data now that the tree is created
-			updateExpandedEntities();
-		};
-		var onTreeNodeExpanded = function (expanded, node) {
-			// save the list of entities that are expanded
-			var tree = $("#entityTree").dynatree("getTree");
-			var list = [];
-			tree.visit( function (tnode) {
-				if (tnode.isExpanded()) {
-					list.push(tnode.data.key)
-				}
-			})
-			localStorage[ListExpandedKey] = JSON.stringify(list)
-
-			if (expanded)
-				onTreeSelected(node);
-		}
-		// a tree node was selected
-		var onTreeSelected = function (selectedNode) {
-			if ($scope.currentMode.id === 'operations')
-				$scope.currentMode = $scope.modes[0];
-			else if ($scope.currentMode.id === 'log')
-				$scope.selectMode($scope.currentMode)
-			else if ($scope.currentMode.id === 'delete') {
-				// clicked on a tree node while on the delete screen -> switch to attribute screen
-				$scope.currentMode = $scope.modes[0];
-			}
-			if (selectedNode.data.typeName === "entity") {
-				$scope.selectedEntity = selectedNode.data.key;
-				$scope.operations = lookupOperations()
-			} else if (selectedNode.data.typeName === 'attribute') {
-				$scope.selectedEntity = selectedNode.parent.data.key;
-				$scope.operations = lookupOperations()
-				$scope.selectedRecordName = selectedNode.data.key;
-				updateDetails(selectedNode.data.details);   // update the table on the right
-				$("#entityTree").dynatree("getRoot").visit(function(node){
-				   node.select(false);
-				});
-				selectedNode.select();
-			}
-			$scope.$apply();
-		}
-		// fill in an empty results recoord based on the entities schema
-		var fromSchema = function (entityName) {
-			var row = {}
-			var schemaEntity = QDRService.schema.entityTypes[entityName]
-			for (attr in schemaEntity.attributes) {
-				var entity = schemaEntity.attributes[attr]
-				var value = ""
-				if (angular.isDefined(entity['default']))
-					value = entity['default']
-				row[attr] = {
-					value: value,
-					type: entity.type,
-					graph: false,
-					title: entity.description,
-					aggregate: false,
-					aggregateTip: '',
-					'default': entity['default']
-				}
-			}
-			return row;
-		}
-		$scope.hasCreate = function () {
-			var schemaEntity = QDRService.schema.entityTypes[$scope.selectedEntity]
-			return (schemaEntity.operations.indexOf("CREATE") > -1)
-		}
-
-		var stopUpdating = function () {
-			if (angular.isDefined(updateIntervalHandle)) {
-				clearInterval(updateIntervalHandle);
-			}
-			updateIntervalHandle = undefined;
-		}
-
-		// the data for the selected entity is available, populate the tree
-		var updateEntityChildren = function (entity, tableRows, expand) {
-			var tree = $("#entityTree").dynatree("getTree");
-			if (!tree.getNodeByKey) {
-				return stopUpdating()
-			}
-			var node = tree.getNodeByKey(entity)
-			var updatedDetails = false;
-			var scrollTreeDiv = $('.qdr-attributes.pane.left .pane-viewport')
-			var scrollTop = scrollTreeDiv.scrollTop();
-			node.removeChildren();
-			if (tableRows.length == 0) {
-			    node.addChild({
-					addClass:   "no-data",
-			        typeName:   "none",
-			        title:      "no data",
-					key:        node.data.key + ".1"
-			    })
-			    if (expand) {
-			        updateDetails(fromSchema(entity));
-       			    $scope.selectedRecordName = entity;
-				}
-			} else {
-				tableRows.forEach( function (row) {
-					var addClass = entity;
-					if (classOverrides[entity]) {
-						addClass += " " + classOverrides[entity](row);
-					}
-					var child = {
-                        typeName:   "attribute",
-                        addClass:   addClass,
-                        key:        row.name.value,
-                        title:      row.name.value,
-                        details:    row
-                    }
-					if (row.name.value === $scope.selectedRecordName) {
-						if (expand)
-							updateDetails(row); // update the table on the right
-						child.select = true;
-						updatedDetails = true;
-					}
-					node.addChild(child)
-				})
-			}
-			// if the selectedRecordName was not found, select the 1st one
-			if (expand && !updatedDetails && tableRows.length > 0) {
-				var row = tableRows[0];
-				$scope.selectedRecordName = row.name.value;
-				var node = tree.getNodeByKey($scope.selectedRecordName);
-				node.select(true);
-				updateDetails(row)  // update the table on the right
-			}
-			scrollTreeDiv.scrollTop(scrollTop)
-		}
-
-		var schemaProps = function (entityName, key, currentNode) {
-	   		var typeMap = {integer: 'number', string: 'text', path: 'text', boolean: 'boolean', map: 'textarea'};
-
-			var entity = QDRService.schema.entityTypes[entityName]
-			var value = entity.attributes[key]
-			// skip identity and depricated fields
-			if (!value)
-				return {input: 'input', type: 'disabled', required: false, selected: "", rawtype: 'string', disabled: true, 'default': ''}
-			var description = value.description || ""
-			var val = value['default'];
-			var disabled = (key == 'identity' || description.startsWith('Deprecated'))
-			// special cases
-			if (entityName == 'log' && key == 'module') {
-				return {input: 'input', type: 'disabled', required: false, selected: "", rawtype: 'string', disabled: true, 'default': ''}
-			}
-			if (entityName === 'linkRoutePattern' && key === 'connector') {
-				// turn input into a select. the values will be populated later
-				value.type = []
-				// find all the connector names and populate the select
-				QDRService.getNodeInfo(currentNode.id, '.connector', ['name'], function (nodeName, dotentity, response) {
-					$scope.detailFields.some( function (field) {
-						if (field.name === 'connector') {
-							field.rawtype = response.results.map (function (result) {return result[0]})
-							return true;
-						}
-					})
-				});
-			}
-			return {    name:       key,
-						humanName:  QDRService.humanify(key),
-                        description:value.description,
-                        type:       disabled ? 'disabled' : typeMap[value.type],
-                        rawtype:    value.type,
-                        input:      typeof value.type == 'string' ? value.type == 'boolean' ? 'boolean' : 'input'
-                                                                  : 'select',
-                        selected:   val ? val : undefined,
-                        'default':  value['default'],
-                        value:      val,
-                        required:   value.required,
-                        unique:     value.unique,
-                        disabled:   disabled
-            };
-		}
-		$scope.getAttributeValue = function (attribute) {
-			var value = attribute.attributeValue;
-			if ($scope.currentMode.op === "CREATE" && attribute.name === 'identity')
-				value = "<assigned by system>"
-			return value;
-		}
-
-		// update the table on the right
-		var updateDetails = function (row) {
-			var details = [];
-			$scope.detailsObject = {};
-			var attrs = Object.keys(row).sort();
-			attrs.forEach( function (attr) {
-				var changed = $scope.detailFields.filter(function (old) {
-					return (old.name === attr) ? old.graph && old.rawValue != row[attr].value : false;
-				})
-				var schemaEntity = schemaProps($scope.selectedEntity, attr, $scope.currentNode)
-				details.push( {
-					attributeName:  QDRService.humanify(attr),
-					attributeValue: attr === 'port' ? row[attr].value : QDRService.pretty(row[attr].value),
-					name:           attr,
-					changed:        changed.length,
-					rawValue:       row[attr].value,
-					graph:          row[attr].graph,
-					title:          row[attr].title,
-					aggregateValue: QDRService.pretty(row[attr].aggregate),
-					aggregateTip:   row[attr].aggregateTip,
-
-					input:          schemaEntity.input,
-					type:           schemaEntity.type,
-					required:       schemaEntity.required,
-					selected:       schemaEntity.selected,
-					rawtype:        schemaEntity.rawtype,
-					disabled:       schemaEntity.disabled,
-					'default':      schemaEntity['default']
-				})
-				$scope.detailsObject[attr] = row[attr].value;
-			})
-			setTimeout(applyDetails, 1, details)
-		}
-
-		var applyDetails = function (details) {
-			$scope.detailFields = details;
-			aggregateColumn();
-			$scope.$apply();
-			// ng-grid bug? the entire table doesn't always draw unless a reflow is triggered;
-			$(window).trigger('resize');
-		}
-
-		var restartUpdate = function () {
-			stopUpdating();
-			updateTableData($scope.selectedEntity, true);
-			updateIntervalHandle = setInterval(updateExpandedEntities, updateInterval);
-		}
-		var updateExpandedEntities = function () {
-			var tree = $("#entityTree").dynatree("getTree");
-			if (tree.visit) {
-				tree.visit( function (node) {
-					if (node.isExpanded()) {
-						updateTableData(node.data.key, node.data.key === $scope.selectedEntity)
-					}
-				})
-			} else {
-				stopUpdating();
-			}
-		}
-
-		$scope.selectNode = function(node) {
-			$scope.selectedNode = node.name;
-			$scope.selectedNodeId = node.id;
-			setCurrentNode();
-			restartUpdate();
-		};
-		$scope.$watch('selectedEntity', function(newValue, oldValue) {
-			if (newValue !== oldValue) {
-				localStorage['QDRSelectedEntity'] = $scope.selectedEntity;
-				restartUpdate();
-				$scope.operations = lookupOperations()
-			}
-		})
-		$scope.$watch('selectedNode', function(newValue, oldValue) {
-		    if (newValue !== oldValue) {
-				localStorage['QDRSelectedNode'] = $scope.selectedNode;
-				localStorage['QDRSelectedNodeId'] = $scope.selectedNodeId;
-			}
-		})
-		$scope.$watch('selectedRecordName', function(newValue, oldValue) {
-			if (newValue != oldValue) {
-				localStorage['QDRSelectedRecordName'] = $scope.selectedRecordName;
-			}
-		})
-
-		/* Called periodically to refresh the data on the page */
-		var updateTableData = function (entity, expand) {
-			if (!QDRService.connected) {
-				// we are no longer connected. bail back to the connect page
-				$location.path("/" + QDR.pluginName + "/connect")
-				$location.search('org', "list");
-				return;
-			}
-			// don't update the data when on the operations tab
-			if ($scope.currentMode.id === 'operations') {
-				return;
-			}
-
-			var gotNodeInfo = function (nodeName, dotentity, response) {
-				var tableRows = [];
-				var records = response.results;
-				var aggregates = response.aggregates;
-				var attributeNames = response.attributeNames;
-				// If !attributeNmes then  there was an error getting the records for this entity
-				if (attributeNames) {
-					var nameIndex = attributeNames.indexOf("name");
-					var identityIndex = attributeNames.indexOf("identity");
-					var ent = QDRService.schema.entityTypes[entity];
-					for (var i=0; i<records.length; ++i) {
-						var record = records[i];
-						var aggregate = aggregates ? aggregates[i] : undefined;
-						var row = {};
-						var rowName;
-						if (nameIndex > -1) {
-							rowName = record[nameIndex];
-							if (!rowName && identityIndex > -1) {
-								rowName = record[nameIndex] = (dotentity + '/' + record[identityIndex])
-							}
-						}
-						if (!rowName) {
-							QDR.log.error("response attributeNames did not contain a name field");
-							console.dump(response.attributeNames);
-							return;
-						}
-						for (var j=0; j<attributeNames.length; ++j) {
-							var col = attributeNames[j];
-							row[col] = {value: record[j], type: undefined, graph: false, title: '', aggregate: '', aggregateTip: ''};
-							if (ent) {
-								var att = ent.attributes[col];
-								if (att) {
-									row[col].type = att.type;
-									row[col].graph = att.graph;
-									row[col].title = att.description;
-
-									if (aggregate) {
-										if (att.graph) {
-											row[col].aggregate = att.graph ? aggregate[j].sum : '';
-											var tip = [];
-											aggregate[j].detail.forEach( function (line) {
-												tip.push(line);
-											})
-											row[col].aggregateTip = angular.toJson(tip);
-										}
-									}
-								}
-							}
-						}
-						tableRows.push(row);
-					}
-				}
-
-				tableRows.sort( function (a, b) { return a.name.value.localeCompare(b.name.value) })
-				setTimeout(selectRow, 0, {entity: dotentity, rows: tableRows, expand: expand});
-			}
-			// if this entity should show an aggregate column, send the request to get the info for this entity from all the nedes
-			if (aggregateEntities.indexOf(entity) > -1) {
-				var nodeInfo = QDRService.topology.nodeInfo();
-				QDRService.getMultipleNodeInfo(Object.keys(nodeInfo), entity, [], gotNodeInfo, $scope.selectedNodeId);
-			} else {
-				QDRService.getNodeInfo($scope.selectedNodeId, entity, [], gotNodeInfo);
-			}
-		};
-
-		// tableRows are the records that were returned, this populates the left hand table on the page
-		var selectRow = function (info) {
-			updateEntityChildren(info.entity, info.rows, info.expand);
-			fixTooltips();
-		}
-
-	    var titleFromAlt = function (alt) {
-			if (alt && alt.length) {
-				var data = angular.fromJson(alt);
-				var table = "<table class='tiptable'><tbody>";
-				data.forEach (function (row) {
-					table += "<tr>";
-					table += "<td>" + row.node + "</td><td align='right'>" + QDRService.pretty(row.val) + "</td>";
-					table += "</tr>"
-				})
-				table += "</tbody></table>"
-				return table;
-			}
-			return '';
-	    }
-		var fixTooltips = function () {
-			if ($('.hastip').length == 0) {
-				setTimeout(fixTooltips, 100);
-				return;
-			}
-			$('.hastip').each( function (i, tip) {
-				var tipset = tip.getAttribute('tipset')
-				if (!tipset) {
-					$(tip).tipsy({html: true, className: 'subTip', opacity: 1, title: function () {
-						return titleFromAlt(this.getAttribute('alt'))
-					} });
-					tip.setAttribute('tipset', true)
-				} else {
-					var title = titleFromAlt(tip.getAttribute('alt'))
-					tip.setAttribute('original-title', title)
-				}
-			})
-		}
-
-		$scope.detailFields = [];
-
-		$scope.addToGraph = function(rowEntity) {
-			var chart = QDRChartService.registerChart(
-				{nodeId: $scope.selectedNodeId,
-				 entity: "." + $scope.selectedEntity,
-				 name:   $scope.selectedRecordName,
-				 attr:    rowEntity.name,
-				 forceCreate: true});
-			doDialog("template-from-script.html", chart);
-		}
-
-		$scope.addAllToGraph = function(rowEntity) {
-			var chart = QDRChartService.registerChart({
-				nodeId:     $scope.selectedNodeId,
-				entity:     $scope.selectedEntity,
-				name:       $scope.selectedRecordName,
-				attr:       rowEntity.name,
-				type:       "rate",
-				rateWindow: udateInterval,
-				visibleDuration: 1,
-				forceCreate: true,
-				aggregate:   true});
-			doDialog("template-from-script.html", chart);
-		}
-
-		$scope.detailCols = [];
-		var aggregateColumn = function () {
-			if ((aggregateEntities.indexOf($scope.selectedEntity) > -1 && $scope.detailCols.length != 3) ||
-				(aggregateEntities.indexOf($scope.selectedEntity) == -1 && $scope.detailCols.length != 2)) {
-				// column defs have to be reassigned and not spliced, so no push/pop
-				 $scope.detailCols = [
-				 {
-					 field: 'attributeName',
-					 displayName: 'Attribute',
-					 cellTemplate: '<div title="{{row.entity.title}}" class="listAttrName">{{row.entity[col.field]}}<i ng-if="row.entity.graph" ng-click="addToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>'
-				 },
-				 {
-					 field: 'attributeValue',
-					 displayName: 'Value',
-					 cellTemplate: '<div class="ngCellText" ng-class="{\'changed\': row.entity.changed == 1}"><span>{{row.getProperty(col.field)}}</span></div>'
-				 }
-				 ]
-				if (aggregateEntities.indexOf($scope.selectedEntity) > -1) {
-					$scope.detailCols.push(
-					 {
-						 width: '10%',
-						 field: 'aggregateValue',
-						 displayName: 'Aggregate',
-						 cellTemplate: '<div class="hastip" alt="{{row.entity.aggregateTip}}"><span ng-class="{\'changed\': row.entity.changed == 1}">{{row.entity[col.field]}}</span><i ng-if="row.entity.graph" ng-click="addAllToGraph(row.entity)" ng-class="{\'icon-bar-chart\': row.entity.graph == true }"></i></div>',
-						 cellClass: 'aggregate'
-					 }
-					)
-				}
-			}
-			if ($scope.selectedRecordName === "")
-				$scope.detailCols = [];
-		}
-
-		// the table on the right of the page contains a row for each field in the selected record in the table on the left
-		$scope.details = {
-			data: 'detailFields',
-			columnDefs: "detailCols",
-			enableColumnResize: true,
-			multiSelect: false,
-			beforeSelectionChange: function() {
-				  return false;
-			}
-		};
-		$scope.$on("$destroy", function( event ) {
-			//QDR.log.debug("scope destroyed for qdrList");
-			stopUpdating();
-		});
-
-		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);
-				//QDR.log.debug(context.message.application_properties.statusDescription)
-			} else {
-				var note = entity + " " + $filter('Pascalcase')($scope.currentMode.op) + "d"
-				Core.notification('success', note);
-				$scope.selectMode($scope.modes[0]);
-				restartUpdate();
-			}
-		}
-		$scope.ok = function () {
-			var attributes = {}
-			$scope.detailFields.forEach( function (field) {
-				var value = field.rawValue;
-				if (field.input === 'input') {
-					if (field.type === 'text' || field.type === 'disabled')
-						value = field.attributeValue;
-				} else if (field.input === 'select') {
-					value = field.selected;
-				} else if (field.input === 'boolean') {
-					value = field.rawValue
-				}
-				if (value === "")
-					value = undefined;
-
-				if ((value && value != field['default']) || field.required || (field.name === 'role')) {
-					if (field.name !== 'identity')
-						attributes[field.name] = value
-				}
-			})
-			QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, gotMethodResponse)
-		}
-		$scope.remove = function () {
-			var attributes = {type: $scope.selectedEntity, name: $scope.selectedRecordName}
-			QDRService.sendMethod($scope.currentNode.id, $scope.selectedEntity, attributes, $scope.currentMode.op, gotMethodResponse)
-		}
-
-		function doDialog(template, chart) {
-		    var d = $dialog.dialog({
-		      backdrop: true,
-		      keyboard: true,
-		      backdropClick: true,
-		      templateUrl: template,
-		      controller: "QDR.ListChartController",
-		      resolve: {
-                 chart: function() {
-                   return chart
-                 },
-                 nodeName: function () {
-                    return $scope.selectedNode
-                 }
-              }
-		    });
-
-		    d.open().then(function(result) { console.log("d.open().then"); });
-
-		};
-	}]);
-
-	QDR.module.controller('QDR.ListChartController', function ($scope, dialog, $dialog, $location, localStorage, QDRChartService, chart, nodeName) {
-	    $scope.chart = chart;
-		$scope.dialogSvgChart = null;
-		var updateTimer = null;
-		$scope.svgDivId = "dialogChart";    // the div id for the svg chart
-
-		$scope.showChartsPage = function () {
-			cleanup();
-			dialog.close(true);
-			$location.path("/" + QDR.pluginName + "/charts");
-		};
-
-		$scope.addHChart = function () {
-	        QDRChartService.addHDash($scope.chart);
-			cleanup();
-	        dialog.close(true);
-		}
-
-		$scope.addToDashboardLink = function () {
-			var href = "#/" + QDR.pluginName + "/charts";
-			var size = angular.toJson({
-	                size_x: 2,
-	                size_y: 2
-	              });
-
-			var params = angular.toJson({chid: $scope.chart.id()});
-	        var title = "Dispatch - " + nodeName;
-		    return "/hawtio/#/dashboard/add?tab=dashboard" +
-		          "&href=" + encodeURIComponent(href) +
-		          "&routeParams=" + encodeURIComponent(params) +
-		          "&title=" + encodeURIComponent(title) +
-		          "&size=" + encodeURIComponent(size);
-	    };
-
-
-		$scope.addChartsPage = function () {
-			QDRChartService.addDashboard($scope.chart);
-		};
-
-		$scope.delChartsPage = function () {
-			QDRChartService.delDashboard($scope.chart);
-		};
-
-		$scope.isOnChartsPage = function () {
-			return $scope.chart.dashboard;
-		}
-
-		var showChart = function () {
-			// the chart divs are generated by angular and aren't available immediately
-			var div = angular.element("#" + $scope.svgDivId);
-			if (!div.width()) {
-				setTimeout(showChart, 100);
-				return;
-			}
-			dialogSvgChart = new QDRChartService.AreaChart($scope.chart);
-			$scope.dialogSvgChart = dialogSvgChart;
-			updateDialogChart();
-		}
-		showChart();
-
-		var updateDialogChart = function () {
-			if ($scope.dialogSvgChart)
-				$scope.dialogSvgChart.tick($scope.svgDivId);
-			if (updateTimer)
-				clearTimeout(updateTimer)
-			updateTimer = setTimeout(updateDialogChart, 1000);
-		}
-
-		var cleanup = function () {
-			if (updateTimer) {
-				clearTimeout(updateTimer);
-				updateTimer = null;
-			}
-			if (!$scope.chart.hdash)
-				QDRChartService.unRegisterChart($scope.chart);     // remove the chart
-
-		}
-		$scope.ok = function () {
-			cleanup();
-			dialog.close(true);
-			//dialogService.cancel("myDialog");
-	    };
-
-	});
-    return QDR;
-
-} (QDR || {}));
-// messaging-ci-01.mw.lab.eng.bos.redhat.com
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrList.js b/console/hawtio/src/main/webapp/plugin/js/qdrList.js
new file mode 120000
index 0000000..3955db5
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrList.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrList.js
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js b/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
new file mode 120000
index 0000000..d1fcf4a
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrListChart.js
@@ -0,0 +1 @@
+../../../../../../stand-alone/plugin/js/qdrListChart.js
\ 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/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrSettings.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrSettings.js b/console/stand-alone/plugin/js/qdrSettings.js
index 950e1c5..e81463a 100644
--- a/console/stand-alone/plugin/js/qdrSettings.js
+++ b/console/stand-alone/plugin/js/qdrSettings.js
@@ -28,7 +28,8 @@ var QDR = (function (QDR) {
    *
    * Controller that handles the QDR settings page
    */
-  QDR.module.controller("QDR.SettingsController", ['$scope', 'QDRService', '$location', function($scope, QDRService, $location) {
+
+  QDR.module.controller("QDR.SettingsController", ['$scope', 'QDRService', '$timeout', '$location', function($scope, QDRService, $timeout, $location) {
 
     $scope.connecting = false;
     $scope.connectionError = false;
@@ -36,38 +37,6 @@ var QDR = (function (QDR) {
     $scope.forms = {};
 
     $scope.formEntity = angular.fromJson(localStorage[QDR.SETTINGS_KEY]) || {address: '', port: '', username: '', password: '', autostart: false};
-    $scope.formConfig = {
-      properties: {
-        address: {
-          description: "Router address",
-          'type': 'java.lang.String',
-          required: true
-        },
-        port: {
-          description: 'Router port',
-          'type': 'Integer',
-          tooltip: 'Ports to connect to, by default 5672'
-        },
-        username: {
-          description: 'User Name',
-          'type': 'java.lang.String'
-        },
-        password: {
-          description: 'Password',
-          'type': 'password'
-        },
-        /*
-        useSSL: {
-          description: 'SSL',
-          'type': 'boolean'
-        },*/
-        autostart: {
-          description: 'Connect at startup',
-          'type': 'boolean',
-          tooltip: 'Whether or not the connection should be started as soon as you log into hawtio'
-        }
-      }
-    };
 
     $scope.$watch('formEntity', function(newValue, oldValue) {
       if (newValue !== oldValue) {
@@ -77,39 +46,106 @@ var QDR = (function (QDR) {
 
     $scope.buttonText = function() {
       if (QDRService.isConnected()) {
-        return "Reconnect";
+        return "Disconnect";
       } else {
         return "Connect";
       }
     };
 
-    
     $scope.connect = function() {
-      if ($scope.settings.$valid) {
-        $scope.connectionError = false;
-        $scope.connecting = true;
-        console.log("attempting to connect");
-        QDRService.addDisconnectAction(function() {
-          //QDR.log.debug("disconnect action called");
-          $scope.connecting = false;
-          $scope.connectionErrorText = QDRService.errorText;
-          $scope.connectionError = true;
-          $scope.$apply();
-        });
+		if (QDRService.connected) {
+			QDRService.disconnect();
+		return;
+		}
+
+		if ($scope.settings.$valid) {
+			$scope.connectionError = false;
+			$scope.connecting = true;
+			$timeout( doConnect )   // timeout so connecting animation can display
+		}
+	}
+
+	var doConnect = function () {
+        if (!$scope.formEntity.address)
+            $scope.formEntity.address = "localhost"
+
+        console.log("attempting to connect to " + $scope.formEntity.address + ':' + $scope.formEntity.port);
+	    QDRService.addDisconnectAction(function () {
+			$timeout( function () {
+				QDR.log.debug("disconnect action called");
+                $scope.connecting = false;
+                $scope.connectionErrorText = QDRService.errorText;
+                $scope.connectionError = true;
+        	})
+	    });
         QDRService.addConnectAction(function() {
           //QDR.log.debug("got connection notification");
-          $scope.connecting = false;
-          //console.log("we were on connect page. let's switch to topo now that we are connected");
-          //QDR.log.debug("location before the connect " + $location.path());
-          $location.path("/overview");
-          //QDR.log.debug("location after the connect " + $location.path());
-          $scope.$apply();
+			$timeout( function () {
+				$scope.connecting = false;
+			})
         });
         QDRService.connect($scope.formEntity);
       }
-    };
 
   }]);
 
+
+QDR.module.directive('posint', function (){
+   return {
+	require: 'ngModel',
+
+	link: function(scope, elem, attr, ctrl) {
+		// input type number allows + and - but we don't want them so filter them out
+		elem.bind('keypress', function (event) {
+			var nkey = !event.charCode ? event.which : event.charCode;
+			var skey = String.fromCharCode(nkey);
+			var nono = "-+.,"
+			if (nono.indexOf(skey) >= 0) {
+				event.preventDefault();
+				return false;
+			}
+			// firefox doesn't filter out non-numeric input. it just sets the ctrl to invalid
+			if (/[\!\@\#\$\%^&*\(\)]/.test(skey) && event.shiftKey || // prevent shift numbers
+				!(                                      // prevent all but the following
+					nkey <= 0 ||                            // arrows
+					nkey == 8 ||                            // delete|backspace
+					nkey == 13 ||                           // enter
+					(nkey >= 37 && nkey <=40) ||            // arrows
+					event.ctrlKey || event.altKey ||        // ctrl-v, etc.
+					/[0-9]/.test(skey))                     // numbers
+				) {
+					event.preventDefault();
+					return false;
+			}
+		})
+		// check the current value of input
+		var _isPortInvalid = function (value) {
+			var port = value + ''
+			var isErrRange = false;
+			// empty string is valid
+			if (port.length !== 0) {
+				var n = ~~Number(port);
+				if (n < 1 || n > 65535) {
+					isErrRange = true;
+				}
+			}
+			ctrl.$setValidity('range', !isErrRange)
+			return isErrRange;
+		}
+
+		//For DOM -> model validation
+		ctrl.$parsers.unshift(function(value) {
+			return _isPortInvalid(value) ? undefined : value;
+		});
+
+		//For model -> DOM validation
+		ctrl.$formatters.unshift(function(value) {
+			_isPortInvalid(value);
+			return value;
+		});
+      }
+   };
+});
+
   return QDR;
 }(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/qdrTopology.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrTopology.js b/console/stand-alone/plugin/js/qdrTopology.js
index 75b8b6e..9ab3689 100644
--- a/console/stand-alone/plugin/js/qdrTopology.js
+++ b/console/stand-alone/plugin/js/qdrTopology.js
@@ -21,20 +21,12 @@ under the License.
  */
 var QDR = (function (QDR) {
 
-  /**
-   * @function TopologyFormController
-   *
-   * @param $scope
-   * @param QDRService
-   *
-   * The controller for the topology page's connection/router info form on the left
-   *
-   */
-	QDR.module.controller('QDR.TopologyFormController', ['$scope', 'QDRService',
-	function ($scope, QDRService) {
+
+	QDR.module.controller('QDR.TopologyFormController', function ($scope, QDRService) {
 
 		$scope.attributes = []
-        var generalCellTemplate = '<div class="ngCellText"><span title="{{row.entity.description}}">{{row.entity.attributeName}}</span></div>';
+        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,
@@ -43,11 +35,12 @@ var QDR = (function (QDR) {
             {
                 field: 'attributeName',
                 displayName: 'Attribute',
-                cellTemplate: generalCellTemplate
+                cellTemplate: nameTemplate
             },
             {
                 field: 'attributeValue',
-                displayName: 'Value'
+                displayName: 'Value',
+				cellTemplate: valueTemplate
             }
             ]
         };
@@ -55,9 +48,9 @@ var QDR = (function (QDR) {
 		$scope.$on('showEntityForm', function (event, args) {
 			var attributes = args.attributes;
 			var entityTypes = QDRService.schema.entityTypes[args.entity].attributes;
-			Object.keys(attributes).forEach( function (attr) {
-				if (entityTypes[attr])
-					attributes[attr].description = entityTypes[attr]
+			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;
@@ -65,53 +58,225 @@ var QDR = (function (QDR) {
 		$scope.$on('showAddForm', function (event) {
 			$scope.form = 'add';
 		})
-		$scope.getTableHeight = function (rows) {
-            return {height: ((rows.length+2) * 30) + "px"};
-        }
+	})
 
-	}])
 
   /**
    * @method TopologyController
    *
    * Controller that handles the QDR topology page
    */
-    QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'uiGridConstants', 'QDRService', '$uibModal', '$location', '$timeout',
-    function($scope, $rootScope, uiGridConstants, QDRService, $uibModal, $location, $timeout) {
-
-		$scope.multiData = [{name: ''}, {name: ''}, {name: ''}]
+    QDR.module.controller("QDR.TopologyController", ['$scope', '$rootScope', 'QDRService', '$location', '$timeout', '$dialog',
+    function($scope, $rootScope, QDRService, $location, $timeout, $dialog) {
+
+        $scope.multiData = []
+        $scope.selectedClient = [];
+        $scope.quiesceState = {}
+        var dontHide = false;
+		$scope.quiesceConnection = function (row) {
+			var entity = row.entity;
+			var state = $scope.quiesceState[entity.connectionId].state;
+			if (state === 'enabled') {
+				// start quiescing all links
+				$scope.quiesceState[entity.connectionId].state = 'quiescing';
+			} else if (state === 'quiesced') {
+				// start reviving all links
+				$scope.quiesceState[entity.connectionId].state = 'reviving';
+			}
+			$scope.multiDetails.updateState(entity);
+			dontHide = true;
+			$scope.multiDetails.selectRow(row.rowIndex, true);
+			$scope.multiDetails.showLinksList(row)
+		}
+		$scope.quiesceDisabled = function (row) {
+			return $scope.quiesceState[row.entity.connectionId].buttonDisabled;
+		}
+		$scope.quiesceText = function (row) {
+			return $scope.quiesceState[row.entity.connectionId].buttonText;
+		}
+		$scope.quiesceClass = function (row) {
+			var stateClassMap = {
+				enabled: 'btn-primary',
+				quiescing: 'btn-warning',
+				reviving: 'btn-warning',
+				quiesced: 'btn-danger'
+			}
+			return stateClassMap[$scope.quiesceState[row.entity.connectionId].state];
+		}
         $scope.multiDetails = {
             data: 'multiData',
+			selectedItems: $scope.selectedClient,
+			multiSelect: false,
+			afterSelectionChange: function (obj) {
+				if (obj.selected && obj.orig) {
+					var detailsDiv = d3.select('#link_details')
+					var isVis = detailsDiv.style('display') === 'block';
+					if (!dontHide && isVis && $scope.connectionId === obj.entity.connectionId) {
+						hideLinkDetails();
+						return;
+					}
+					dontHide = false;
+					$scope.multiDetails.showLinksList(obj)
+				}
+			},
+			showLinksList: function (obj) {
+				$scope.linkData = obj.entity.linkData;
+				$scope.connectionId = obj.entity.connectionId;
+				var visibleLen = Math.min(obj.entity.linkData.length, 10)
+				var left = parseInt(d3.select('#multiple_details').style("left"))
+				var detailsDiv = d3.select('#link_details')
+	            detailsDiv
+	                .style({
+	                    display: 'block',
+	                    opacity: 1,
+	                    left: (left + 20) + "px",
+	                    top:  (mouseY + 20 + $(document).scrollTop()) + "px",
+	                    height: (visibleLen + 1) * 30 + "px", // +1 for the header row
+	                    'overflow-y': obj.entity.linkData > 10 ? 'scroll' : 'hidden'})
+			},
+			updateState: function (entity) {
+				var state = $scope.quiesceState[entity.connectionId].state
+
+				// count enabled and disabled links for this connection
+				var enabled = 0, disabled = 0;
+				entity.linkData.forEach ( function (link) {
+					if (link.adminStatus === 'enabled')
+						++enabled;
+					if (link.adminStatus === 'disabled')
+						++disabled;
+				})
+
+				var linkCount = entity.linkData.length;
+				// if state is quiescing and any links are enabled, button should say 'Quiescing' and be disabled
+				if (state === 'quiescing' && (enabled > 0)) {
+					$scope.quiesceState[entity.connectionId].buttonText = 'Quiescing';
+					$scope.quiesceState[entity.connectionId].buttonDisabled = true;
+				} else
+				// if state is enabled and all links are disabled, button should say Revive and be enabled. set state to quisced
+				// if state is quiescing and all links are disabled, button should say 'Revive' and be enabled. set state to quiesced
+				if ((state === 'quiescing' || state === 'enabled') && (disabled === linkCount)) {
+					$scope.quiesceState[entity.connectionId].buttonText = 'Revive';
+					$scope.quiesceState[entity.connectionId].buttonDisabled = false;
+					$scope.quiesceState[entity.connectionId].state = 'quiesced'
+				} else
+				// if state is reviving and any links are disabled, button should say 'Reviving' and be disabled
+				if (state === 'reviving' && (disabled > 0)) {
+					$scope.quiesceState[entity.connectionId].buttonText = 'Reviving';
+					$scope.quiesceState[entity.connectionId].buttonDisabled = true;
+				} else
+				// if state is reviving or quiesced and all links are enabled, button should say 'Quiesce' and be enabled. set state to enabled
+				if ((state === 'reviving' || state === 'quiesced') && (enabled === linkCount)) {
+					$scope.quiesceState[entity.connectionId].buttonText = 'Quiesce';
+					$scope.quiesceState[entity.connectionId].buttonDisabled = false;
+					$scope.quiesceState[entity.connectionId].state = 'enabled'
+				}
+			},
             columnDefs: [
             {
                 field: 'host',
-                displayName: 'Host'
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+                displayName: 'Connection host'
             },
             {
                 field: 'user',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
                 displayName: 'User'
             },
 			{
 				field: 'properties',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
 				displayName: 'Properties'
-			},
+			}/*,
 			{
-				field: 'isEncrypted',
-				displayName: 'Encrypted'
-			}
+				cellClass: 'gridCellButton',
+				cellTemplate: '<button title="{{quiesceText(row)}} the links" type="button" ng-class="quiesceClass(row)" class="btn" ng-click="$event.stopPropagation();quiesceConnection(row)" ng-disabled="quiesceDisabled(row)">{{quiesceText(row)}}</button>'
+			}*/
             ]
         };
+		$scope.linkData = [];
+		$scope.quiesceLinkClass = function (row) {
+			var stateClassMap = {
+				enabled: 'btn-primary',
+				disabled: 'btn-danger'
+			}
+			return stateClassMap[row.entity.adminStatus]
+			//return stateClassMap[$scope.quiesceState[row.entity.connectionId].linkStates[row.entity.identity]];
+		}
+		$scope.quiesceLink = function (row) {
+			var state = row.entity.adminStatus === 'enabled' ? 'disabled' : 'enabled';
+			$scope.quiesceState[row.entity.connectionId].linkStates[row.entity.identity] = state;
+		}
+		$scope.quiesceLinkDisabled = function (row) {
+			return false;
+		}
+		$scope.quiesceLinkText = function (row) {
+			return row.entity.adminStatus === 'disabled' ? "Revive" : "Quiesce";
+		}
+		$scope.linkDetails = {
+			data: 'linkData',
+            columnDefs: [
+			{
+				field: 'adminStatus',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				displayName: 'Admin stat'
+			},
+			{
+				field: 'dir',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				displayName: 'dir'
+			},
+			{
+				field: 'owningAddr',
+                cellTemplate: "titleCellTemplate.html",
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				displayName: 'Address'
+			},
+			{
+				field: 'deliveryCount',
+				displayName: 'Delivered',
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				cellClass: 'grid-values'
+
+			},
+			{
+				field: 'undeliveredCount',
+				displayName: 'Undelivered',
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				cellClass: 'grid-values'
+			},
+			{
+				field: 'unsettledCount',
+				displayName: 'Unsettled',
+				headerCellTemplate: 'titleHeaderCellTemplate.html',
+				cellClass: 'grid-values'
+			}/*,
+
+			{
+				cellClass: 'gridCellButton',
+				cellTemplate: '<button title="{{quiesceLinkText(row)}} this link" type="button" ng-class="quiesceLinkClass(row)" class="btn" ng-click="quiesceLink(row)" ng-disabled="quiesceLinkDisabled(row)">{{quiesceLinkText(row)}}</button>'
+			}*/
+			]
+		}
 
 		if (!QDRService.connected) {
 			// we are not connected. we probably got here from a bookmark or manual page reload
-			$location.path("/dispatch_plugin/connect")
-			$location.search('org', "topology");
+			QDRService.redirectWhenConnected("topology");
 			return;
 		}
+		// we are currently connected. setup a handler to get notified if we are ever disconnected
+		QDRService.addDisconnectAction( function () {
+			QDRService.redirectWhenConnected("topology");
+			$scope.$apply();
+		})
 
-		QDR.log.debug("started QDR.TopologyController with urlPrefix: " + $location.absUrl());
-		//var urlPrefix = $location.absUrl();
-		var urlPrefix = window.location.pathname;
+		var urlPrefix = $location.absUrl();
+		urlPrefix = urlPrefix.split("#")[0]
+		QDR.log.debug("started QDR.TopologyController with urlPrefix: " + urlPrefix);
 
 		$scope.addingNode = {
 			step: 0,
@@ -139,7 +304,6 @@ var QDR = (function (QDR) {
 
 	    $scope.modes = [
 	    	{title: 'Topology view', name: 'Diagram', right: false},
-	    	/* {title: '3D Globe view', name: 'Globe', right: false}, */
 	    	/* {title: 'Add a new router node', name: 'Add Router', right: true} */
 	    	];
 		$scope.mode = "Diagram";
@@ -300,7 +464,6 @@ var QDR = (function (QDR) {
             $(".contextMenu").fadeOut(200);
         });
 
-
 		// set up SVG for D3
 	    var width, height;
 	    var tpdiv = $('#topology');
@@ -310,7 +473,14 @@ var QDR = (function (QDR) {
 	    var radius = 25;
 	    var radiusNormal = 15;
 	    width = tpdiv.width() - gap;
-	    height = $(document).height() - gap;
+	    var top = tpdiv.offset().top
+		var tpform = $('#topologyForm')
+		var tpformHeight = tpform.height()
+	    height = Math.max(window.innerHeight, tpformHeight + top) - top - gap;
+		if (width < 10) {
+			QDR.log.info("page width and height are abynormal: returning before very bad things happen")
+			return;
+		}
 
 	    var svg, lsvg;
 		var force;
@@ -372,13 +542,8 @@ var QDR = (function (QDR) {
             }
         }
 
-		var cities = ["Raleigh"];
-		var possibleCities = ["Boston","Tel Aviv-Yafo", "Brno", "Toronto", "Beijing", , "Ashburn", "Raleigh"]
-		//var drag;
-		// create an bare svg element and
 		// initialize the nodes and links array from the QDRService.topology._nodeInfo object
 		var initForceGraph = function () {
-            //QDR.log.debug("initForceGraph called");
 			nodes = [];
 			links = [];
 
@@ -449,7 +614,6 @@ var QDR = (function (QDR) {
 			// initialize the list of links
 			var source = 0;
 			var client = 1;
-			cities = ["Raleigh"];
 			for (var id in nodeInfo) {
 				var onode = nodeInfo[id];
 				var conns = onode['.connection'].results;
@@ -484,11 +648,13 @@ var QDR = (function (QDR) {
 						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 = role === 'normal' ? (properties.console_identifier == 'Dispatch console' ? 'console' : 'client') : 'broker';
+						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;
@@ -498,7 +664,6 @@ var QDR = (function (QDR) {
 								getLink(parent, nodes.length-1, dir);
 								client++;
 							} else {
-
 								normalsParent[nodeType].normals.push(node)
 							}
 						} else {
@@ -525,9 +690,6 @@ var QDR = (function (QDR) {
 				.on('tick', tick)
 				.start()
 
-			//drag = force.drag()
-            //    .on("dragstart", dragstart);
-
 			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
@@ -573,217 +735,6 @@ var QDR = (function (QDR) {
 
 		}
 
-        var initGlobe = function (clients) {
-			d3.select(window)
-				.on("mousemove", mousemove)
-				.on("mouseup", mouseup);
-
-			var width = 960,
-				height = 500;
-
-			var proj = d3.geo.orthographic()
-				.scale(220)
-				.translate([width / 2, height / 2])
-				.clipAngle(90);
-
-			var path = d3.geo.path().projection(proj).pointRadius(1.5);
-
-			var links = [],
-				arcLines = [];
-
-			var graticule = d3.geo.graticule();
-			d3.select("#geology svg").remove();
-			var svg = d3.select("#geology").append("svg")
-				.attr("width", width)
-				.attr("height", height)
-				.on("mousedown", mousedown);
-
-			queue()
-				.defer(d3.json, "plugin/data/world-110m.json")
-				.defer(d3.json, "plugin/data/places1.json")
-				.await(ready);
-
-			function ready(error, world, places) {
-			  var ocean_fill = svg.append("defs").append("radialGradient")
-					.attr("id", "ocean_fill")
-					.attr("cx", "75%")
-					.attr("cy", "25%");
-				  ocean_fill.append("stop").attr("offset", "5%").attr("stop-color", "#fff");
-				  ocean_fill.append("stop").attr("offset", "100%").attr("stop-color", "#eef");
-
-			  var globe_highlight = svg.append("defs").append("radialGradient")
-					.attr("id", "globe_highlight")
-					.attr("cx", "75%")
-					.attr("cy", "25%");
-				  globe_highlight.append("stop")
-					.attr("offset", "5%").attr("stop-color", "#ffd")
-					.attr("stop-opacity","0.6");
-				  globe_highlight.append("stop")
-					.attr("offset", "100%").attr("stop-color", "#ba9")
-					.attr("stop-opacity","0.1");
-
-			  var globe_shading = svg.append("defs").append("radialGradient")
-					.attr("id", "globe_shading")
-					.attr("cx", "55%")
-					.attr("cy", "45%");
-				  globe_shading.append("stop")
-					.attr("offset","30%").attr("stop-color", "#fff")
-					.attr("stop-opacity","0")
-				  globe_shading.append("stop")
-					.attr("offset","100%").attr("stop-color", "#505962")
-					.attr("stop-opacity","0.2")
-
-			  var drop_shadow = svg.append("defs").append("radialGradient")
-					.attr("id", "drop_shadow")
-					.attr("cx", "50%")
-					.attr("cy", "50%");
-				  drop_shadow.append("stop")
-					.attr("offset","20%").attr("stop-color", "#000")
-					.attr("stop-opacity",".5")
-				  drop_shadow.append("stop")
-					.attr("offset","100%").attr("stop-color", "#000")
-					.attr("stop-opacity","0")
-
-			  svg.append("ellipse")
-				.attr("cx", 440).attr("cy", 450)
-				.attr("rx", proj.scale()*.90)
-				.attr("ry", proj.scale()*.25)
-				.attr("class", "noclicks")
-				.style("fill", "url("+urlPrefix+"#drop_shadow)");
-
-			  svg.append("circle")
-				.attr("cx", width / 2).attr("cy", height / 2)
-				.attr("r", proj.scale())
-				.attr("class", "noclicks")
-				.style("fill", "url("+urlPrefix+"#ocean_fill)");
-
-			  svg.append("path")
-				.datum(topojson.object(world, world.objects.land))
-				.attr("class", "land noclicks")
-				.attr("d", path);
-
-			  svg.append("path")
-				.datum(graticule)
-				.attr("class", "graticule noclicks")
-				.attr("d", path);
-
-			  svg.append("circle")
-				.attr("cx", width / 2).attr("cy", height / 2)
-				.attr("r", proj.scale())
-				.attr("class","noclicks")
-				.style("fill", "url("+urlPrefix+"#globe_highlight)");
-
-			  svg.append("circle")
-				.attr("cx", width / 2).attr("cy", height / 2)
-				.attr("r", proj.scale())
-				.attr("class","noclicks")
-				.style("fill", "url("+urlPrefix+"#globe_shading)");
-
-			  var filtered = places.features.filter( function (feature) {
-			    return clients.indexOf(feature.properties.NAME) > -1
-			  })
-			  svg.append("g").attr("class","points")
-				  .selectAll("text").data(filtered)
-				.enter().append("path")
-				  .attr("class", "point")
-				  .attr("d", path);
-
-				svg.append("g").attr("class","labels")
-				  .selectAll("text").data(filtered)
-				  .enter().append("text")
-				  .attr("class", "label")
-				  .text(function(d) { return d.properties.NAME })
-
-				position_labels();
-
-			  // spawn links between cities as source/target coord pairs
-			  places.features.forEach(function(a, i) {
-				if (clients.indexOf(a.properties.NAME) > -1) {
-					places.features.forEach(function(b, j) {
-					  if (b.properties.NAME === 'Raleigh') {
-					  if (j > i) {	// avoid duplicates
-						links.push({
-						  source: a.geometry.coordinates,
-						  target: b.geometry.coordinates
-						});
-					  }
-					  }
-					});
-				}
-			  });
-
-			  // build geoJSON features from links array
-			  links.forEach(function(e,i,a) {
-				var feature =   { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [e.source,e.target] }}
-				arcLines.push(feature)
-			  })
-
-			  svg.append("g").attr("class","arcs")
-				.selectAll("path").data(arcLines)
-				.enter().append("path")
-				  .attr("class","arc")
-				  .attr("d",path)
-			  refresh();
-			}
-
-			function position_labels() {
-			  var centerPos = proj.invert([width/2,height/2]);
-
-			  var arc = d3.geo.greatArc();
-
-			  svg.selectAll(".label")
-				.attr("transform", function(d) {
-				  var loc = proj(d.geometry.coordinates),
-					x = loc[0],
-					y = loc[1];
-				  var offset = x < width/2 ? -5 : 5;
-				  return "translate(" + (x+offset) + "," + (y-2) + ")"
-				})
-				.style("display",function(d) {
-				  var d = arc.distance({source: d.geometry.coordinates, target: centerPos});
-				  return (d > 1.57) ? 'none' : 'inline';
-				})
-			}
-
-			function refresh() {
-			  svg.selectAll(".land").attr("d", path);
-			  svg.selectAll(".point").attr("d", path);
-			  svg.selectAll(".graticule").attr("d", path);
-			  svg.selectAll(".arc").attr("d", path);
-			  position_labels();
-			}
-
-			// modified from http://bl.ocks.org/1392560
-			var m0, o0;
-			o0 = angular.fromJson(localStorage["QDR.rotate"]);
-			if (o0)
-				proj.rotate(o0);
-
-			function mousedown() {
-			  m0 = [d3.event.pageX, d3.event.pageY];
-			  o0 = proj.rotate();
-			  d3.event.preventDefault();
-			}
-			function mousemove() {
-			  if (m0) {
-				var m1 = [d3.event.pageX, d3.event.pageY]
-				  , o1 = [o0[0] + (m1[0] - m0[0]) / 6, o0[1] + (m0[1] - m1[1]) / 6];
-				o1[1] = o1[1] > 30  ? 30  :
-						o1[1] < -30 ? -30 :
-						o1[1];
-				proj.rotate(o1);
-				refresh();
-			  }
-			}
-			function mouseup() {
-			  if (m0) {
-				mousemove();
-				m0 = null;
-				localStorage["QDR.rotate"] = angular.toJson(proj.rotate());
-			  }
-			}
-        }
-
 		function updateForm (key, entity, resultIndex) {
 			var nodeInfo = QDRService.topology.nodeInfo();
 			var onode = nodeInfo[key]
@@ -818,7 +769,7 @@ var QDR = (function (QDR) {
 					})
 					// add as 2nd row
 					if (ports.length)
-						attributes.splice(1, 0, {attributeName: 'Listening on', attributeValue: ports});
+						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})
@@ -848,7 +799,7 @@ var QDR = (function (QDR) {
 				}
 				nodeIndex++
 			}
-            QDR.log.warn("unable to find containerIndex for " + _id);
+            //QDR.log.warn("unable to find containerIndex for " + _id);
             return -1;
         }
 
@@ -899,38 +850,59 @@ var QDR = (function (QDR) {
 
 	    // 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 = d.target.x - d.source.x,
-	                deltaY = d.target.y - d.source.y,
+	            var deltaX = dtx - dsx,
+	                deltaY = dty - dsy,
 	                dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
 	                normX = deltaX / dist,
 	                normY = deltaY / dist;
-	                var sourcePadding, targetPadding;
-	                if (d.target.nodeType == "inter-router") {
-						//                       right arrow  left line start
-						sourcePadding = d.left ? radius + 8  : radius;
-						//                      left arrow      right line start
-						targetPadding = d.right ? radius + 16 : radius;
-	                } else {
-						sourcePadding = d.left ? radiusNormal + 18  : radiusNormal;
-						targetPadding = d.right ? radiusNormal + 16 : radiusNormal;
-	                }
-	                var sourceX = d.source.x + (sourcePadding * normX),
-	                sourceY = d.source.y + (sourcePadding * normY),
-	                targetX = d.target.x - (targetPadding * normX),
-	                targetY = d.target.y - (targetPadding * normY);
+	                var sourceX = dsx + (sourcePadding * normX),
+	                sourceY = dsy + (sourcePadding * normY),
+	                targetX = dtx - (targetPadding * normX),
+	                targetY = dty - (targetPadding * normY);
+					sourceX = Math.max(0, Math.min(width, sourceX))
+					sourceY = Math.max(0, Math.min(width, sourceY))
+					targetX = Math.max(0, Math.min(width, targetX))
+					targetY = Math.max(0, Math.min(width, targetY))
+
 	            return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
 	        });
 
-	        circle.attr('transform', function (d) {
-	            d.x = Math.max(d.x, radiusNormal * 2);
-	            d.y = Math.max(d.y, radiusNormal * 2);
-	            return 'translate(' + d.x + ',' + d.y + ')';
-	        });
 	        if (!animate) {
 	            animate = true;
 	            force.stop();
@@ -961,7 +933,7 @@ var QDR = (function (QDR) {
             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], "routerId" );
+                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");
@@ -995,6 +967,14 @@ var QDR = (function (QDR) {
             return null;
         }
 
+		function clearPopups() {
+            d3.select("#crosssection").style("display", "none");
+			$('.hastip').empty();
+            d3.select("#multiple_details").style("display", "none")
+            d3.select("#link_details").style("display", "none")
+			d3.select('#node_context_menu').style('display', 'none');
+
+		}
 		function removeCrosssection() {
 			setTimeout(function () {
 				d3.select("[id^=tooltipsy]").remove()
@@ -1013,6 +993,16 @@ var QDR = (function (QDR) {
                 .style("opacity", 0)
                 .each("end", function (d) {
                     d3.select("#multiple_details").style("display", "none")
+                    stopUpdateConnectionsGrid();
+                })
+			hideLinkDetails();
+		}
+		function hideLinkDetails() {
+            d3.select("#link_details").transition()
+                .duration(500)
+                .style("opacity", 0)
+                .each("end", function (d) {
+                    d3.select("#link_details").style("display", "none")
                 })
 		}
 
@@ -1053,7 +1043,7 @@ var QDR = (function (QDR) {
 				    }
 				    return;
 				  }
-				        //QDR.log.debug("showing connections form");
+			        //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
@@ -1078,7 +1068,8 @@ var QDR = (function (QDR) {
                             left = d.target;
                             resultIndex = left.resultIndex;
                         }
-                        updateForm(left.key, 'connection', resultIndex);
+						if (resultIndex)
+                            updateForm(left.key, 'connection', resultIndex);
                     }
 
 					mousedown_link = d;
@@ -1110,7 +1101,9 @@ var QDR = (function (QDR) {
                 })
                 .on("click", function (d) {
                     dblckickPos = d3.mouse(this);
+                    QDR.log.debug("dblckickPos is [" + dblckickPos[0] + ", " + dblckickPos[1] + "]")
                     d3.event.stopPropagation();
+                    clearPopups();
                     var diameter = 400;
                     var format = d3.format(",d");
                     var pack = d3.layout.pack()
@@ -1118,16 +1111,13 @@ var QDR = (function (QDR) {
                         .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)");
 
-					svg.on('click', function (d) {
-						removeCrosssection();
-					})
-
 					var root = {
 						name: "links between " + d.source.name + " and " + d.target.name,
 						children: []
@@ -1189,6 +1179,7 @@ var QDR = (function (QDR) {
 	                node.append("circle")
 	                      .attr("r", function(d) { return d.r; });
 
+//	                node.filter(function(d) { return !d.children; }).append("text")
 	                node.append("text")
 	                      .attr("dy", function (d) { return d.children ? "-10em" : ".3em"})
 	                      .style("text-anchor", "middle")
@@ -1203,7 +1194,6 @@ var QDR = (function (QDR) {
 					svgg.transition().attr("transform", "translate(2,2) scale(1)")
                 })
 
-
 	        // remove old links
 	        path.exit().remove();
 
@@ -1227,15 +1217,13 @@ var QDR = (function (QDR) {
 				// add new circles and set their attr/class/behavior
 		        return g.append('svg:circle')
 		            .attr('class', 'node')
-		            .attr('r', function (d) {
-		                return radii[d.nodeType];
-		            })
+		            .attr('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 d.properties.console_identifier == 'Dispatch console' } )
+	                .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 } )
@@ -1329,8 +1317,7 @@ var QDR = (function (QDR) {
                         selected_node = null;
                     }
                     else {
-						// don't select nodes that represent multiple clients/consoles
-                        if (!d.normals || d.normals.length < 2)
+                        if (d.nodeType !== 'normal' && d.nodeType !== 'on-demand')
                             selected_node = mousedown_node;
                     }
                     for (var i=0; i<links.length; ++i) {
@@ -1363,7 +1350,10 @@ var QDR = (function (QDR) {
 
                 })
                 .on("click", function (d) {
-					if (!d.normals || d.normals.length < 2) {
+                    // clicked on a circle
+                    clearPopups();
+					if (!d.normals) {
+						// circle was a router or a broker
 			            if ( QDRService.isArtemis(d) && Core.ConnectionName === 'Artemis' ) {
 							$location.path('/jmx/attributes?tab=artemis&con=Artemis')
 						}
@@ -1371,19 +1361,7 @@ var QDR = (function (QDR) {
 					}
                     clickPos = d3.mouse(this);
                     d3.event.stopPropagation();
-                    $scope.multiData = []
-                    d.normals.forEach( function (n) {
-                        $scope.multiData.push(n)
-                    })
-                    $scope.$apply();
-                    d3.select('#multiple_details')
-                        .style({
-                            display: 'block',
-                            opacity: 1,
-                            height: (d.normals.length + 1) * 30 + "px",
-                            'overflow-y': d.normals.length > 10 ? 'scroll' : 'hidden',
-		                    left: (mouseX + $(document).scrollLeft()) + "px",
-                            top:  (mouseY + $(document).scrollTop()) + "px"})
+					startUpdateConnectionsGrid(d);
 				})
 
 			var appendContent = function (g) {
@@ -1400,13 +1378,13 @@ var QDR = (function (QDR) {
 		                    y = 4;
 		                return y;})
 		            .attr('class', 'id')
-	                .classed('console', function(d) { return d.properties.console_identifier == 'Dispatch console' } )
+   	                .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 (d.properties.console_identifier == 'Dispatch console') {
+		                if (QDRService.isConsole(d)) {
 		                    return '\uf108'; // icon-desktop for this console
 		                }
 						if (QDRService.isArtemis(d)) {
@@ -1420,6 +1398,7 @@ var QDR = (function (QDR) {
 		                return d.name.length>7 ? d.name.substr(0,6)+'...' : d.name;
 		        });
 			}
+
 			appendContent(g)
 
 			var appendTitle = function (g) {
@@ -1427,7 +1406,7 @@ var QDR = (function (QDR) {
 	                var x = '';
 	                if (d.normals && d.normals.length > 1)
 	                    x = " x " + d.normals.length;
-		            if (d.properties.console_identifier == 'Dispatch console') {
+		            if (QDRService.isConsole(d)) {
 	                    return 'Dispatch console' + x
 	                }
 		            if (d.properties.product == 'qpid-cpp') {
@@ -1446,12 +1425,16 @@ var QDR = (function (QDR) {
 
 			// add subcircles
 			svg.selectAll('.subcircle').remove();
-
-			svg.selectAll('.multiple')
-				.insert('svg:circle', '.normal')
-					.attr('class', 'subcircle')
-					.attr('r', 18)
-
+			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 = [];
@@ -1477,6 +1460,7 @@ var QDR = (function (QDR) {
 					// 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)
@@ -1486,10 +1470,18 @@ var QDR = (function (QDR) {
 				.attr('class', "label")
 				.text(function (d) {return d.key })
 			lsvg.exit().remove();
-			var svgEl = document.getElementById("svglegend"),
-				bb = svgEl.getBBox();
-			svgEl.style.height = bb.y + bb.height;
-			svgEl.style.width = bb.x + bb.width;
+			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;
@@ -1502,6 +1494,124 @@ var QDR = (function (QDR) {
 
 	    }
 
+		var startUpdateConnectionsGrid = function (d) {
+			var extendConnections = function () {
+				$scope.multiData = []
+				var normals = d.normals;
+				// find updated normals for d
+		        d3.selectAll('.normal')
+                    .each(function(newd) {
+                        if (newd.id == d.id && newd.name == d.name) {
+                            normals = newd.normals;
+                        }
+                    });
+				if (normals) {
+					normals.forEach( function (n) {
+						var nodeInfo = QDRService.topology.nodeInfo();
+						var links = nodeInfo[n.key]['.router.link'];
+						var linkTypeIndex = links.attributeNames.indexOf('linkType');
+						var connectionIdIndex = links.attributeNames.indexOf('connectionId');
+						n.linkData = [];
+						links.results.forEach( function (link) {
+							if (link[linkTypeIndex] === 'endpoint' && link[connectionIdIndex] === n.connectionId) {
+								var l = {};
+								l.owningAddr = QDRService.valFor(links.attributeNames, link, 'owningAddr');
+								l.dir = QDRService.valFor(links.attributeNames, link, 'linkDir');
+								if (l.owningAddr && l.owningAddr.length > 2)
+									if (l.owningAddr[0] === 'M')
+										l.owningAddr = l.owningAddr.substr(2)
+									else
+										l.owningAddr = l.owningAddr.substr(1)
+
+								l.deliveryCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'deliveryCount'));
+								l.undeliveredCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'undeliveredCount'));
+								l.unsettledCount = QDRService.pretty(QDRService.valFor(links.attributeNames, link, 'unsettledCount'));
+								l.adminStatus = QDRService.valFor(links.attributeNames, link, 'adminStatus');
+								l.identity = QDRService.valFor(links.attributeNames, link, 'identity')
+								l.connectionId = QDRService.valFor(links.attributeNames, link, 'connectionId')
+
+								// TODO: remove this fake quiescing/reviving logic when the routers do the work
+								initConnState(n.connectionId)
+								if ($scope.quiesceState[n.connectionId].linkStates[l.identity])
+									l.adminStatus = $scope.quiesceState[n.connectionId].linkStates[l.identity];
+								if ($scope.quiesceState[n.connectionId].state == 'quiescing') {
+									if (l.adminStatus === 'enabled') {
+										// 25% chance of switching
+										var chance = Math.floor(Math.random() * 2);
+										if (chance == 1) {
+											l.adminStatus = 'disabled';
+											$scope.quiesceState[n.connectionId].linkStates[l.identity] = 'disabled';
+										}
+									}
+								}
+								if ($scope.quiesceState[n.connectionId].state == 'reviving') {
+									if (l.adminStatus === 'disabled') {
+										// 25% chance of switching
+										var chance = Math.floor(Math.random() * 2);
+										if (chance == 1) {
+											l.adminStatus = 'enabled';
+											$scope.quiesceState[n.connectionId].linkStates[l.identity] = 'enabled';
+										}
+									}
+								}
+								QDR.log.debug("pushing link state for " + l.owningAddr + " status: "+ l.adminStatus)
+
+
+								n.linkData.push(l)
+							}
+						})
+						$scope.multiData.push(n)
+						if (n.connectionId == $scope.connectionId)
+							$scope.linkData = n.linkData;
+						initConnState(n.connectionId)
+						$scope.multiDetails.updateState(n)
+					})
+				}
+				$scope.$apply();
+
+	            d3.select('#multiple_details')
+	                .style({
+	                    height: (normals.length + 1) * 30 + "px",
+	                    'overflow-y': normals.length > 10 ? 'scroll' : 'hidden'
+					})
+			}
+
+	        QDRService.addUpdatedAction("normalsStats", extendConnections)
+			extendConnections();
+			clearPopups();
+			var display = 'block'
+			var left = mouseX + $(document).scrollLeft()
+			if (d.normals.length === 1) {
+				display = 'none'
+				left = left - 30;
+				mouseY = mouseY - 20
+			}
+            d3.select('#multiple_details')
+                .style({
+                    display: display,
+                    opacity: 1,
+                    left: (mouseX + $(document).scrollLeft()) + "px",
+                    top:  (mouseY + $(document).scrollTop()) + "px"})
+            if (d.normals.length === 1) {
+				// simulate a click on the connection to popup the link details
+                $scope.multiDetails.showLinksList( {entity: d} )
+            }
+		}
+		var stopUpdateConnectionsGrid = function () {
+	        QDRService.delUpdatedAction("normalsStats");
+		}
+
+		var initConnState = function (id) {
+			if (!angular.isDefined($scope.quiesceState[id])) {
+				$scope.quiesceState[id] = {
+					state:          'enabled',
+					buttonText:     'Quiesce',
+					buttonDisabled: false,
+					linkStates:     {}
+				}
+			}
+		}
+
         function nextHop(thisNode, d) {
             if ((thisNode) && (thisNode != d)) {
                 var target = findNextHopNode(thisNode, d);
@@ -1541,7 +1651,6 @@ var QDR = (function (QDR) {
                 d3.select("#svg_legend svg").remove();
                 animate = true;
                 initForceGraph();
-                initGlobe(cities);
                 //if ($location.path().startsWith("/topology"))
                 //    Core.notification('info', "Qpid dispatch router topology changed");
 
@@ -1609,29 +1718,31 @@ var QDR = (function (QDR) {
 		saveChanged();
         QDRService.startUpdating();
 
-		initGlobe(cities)//, "Boston", "Tel Aviv-Yafo"]);
-
 	    function doAddDialog(NewRouterName) {
-		    var modalInstance = $uibModal.open({
-		        animation: true,
+		    var d = $dialog.dialog({
+				dialogClass: "modal dlg-large",
+				backdrop: true,
+				keyboard: true,
+				backdropClick: true,
 		        controller: 'QDR.NodeDialogController',
 		        templateUrl: 'node-config-template.html',
-		        size: 'lg',
 		        resolve: {
 		            newname: function () {
 		                return NewRouterName;
 		            }
 		        }
 		    });
-		    modalInstance.result.then(function (result) {
+		    d.open().then(function (result) {
 				if (result)
-					setTimeout(doDownloadDialog, 100, result);
+					doDownloadDialog(result);
 		    });
         };
 
 	    function doDownloadDialog(result) {
-		    var modalInstance = $uibModal.open({
-		        animation: true,
+		    d = $dialog.dialog({
+				backdrop: true,
+				keyboard: true,
+				backdropClick: true,
 				controller: 'QDR.DownloadDialogController',
 		        templateUrl: 'download-dialog-template.html',
 		        resolve: {
@@ -1640,12 +1751,16 @@ var QDR = (function (QDR) {
 		            }
 		        }
 		    });
+		    d.open().then(function (result) {
+		    //QDR.log.debug("download dialog done")
+		    })
+            if (!$scope.$$phase) $scope.$apply()
         };
   }]);
 
-  QDR.module.controller("QDR.NodeDialogController", function($scope, QDRService, $uibModalInstance, newname) {
+  QDR.module.controller("QDR.NodeDialogController", function($scope, QDRService, dialog, newname) {
    		var schema = QDRService.schema;
-   		var myEntities = ['container', 'router', 'log', 'listener' ];
+   		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();
@@ -1793,7 +1908,7 @@ var QDR = (function (QDR) {
 				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 == 'addr'})[0].value
+				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';
@@ -1819,8 +1934,8 @@ var QDR = (function (QDR) {
 				var res = listener.results[i];
 				var role = QDRService.valFor(attrs, res, 'role');
 				if (role == 'inter-router') {
-					ediv.attributes.filter(function( attr ) { return attr.name == 'addr'})[0].value =
-						QDRService.valFor(attrs, res, 'addr')
+					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;
@@ -1829,7 +1944,7 @@ var QDR = (function (QDR) {
 			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 == 'addr'})[0].value = myAddr;
+   				ediv.attributes.filter(function (attr) { return attr.name == 'host'})[0].value = myAddr;
 
 				separatedEntities.push(ediv)
 			}
@@ -1845,9 +1960,6 @@ var QDR = (function (QDR) {
 
 		})
 
-        $scope.cancel = function () {
-            $uibModalInstance.close()
-        };
 		$scope.testPattern = function (attr) {
 			if (attr.rawtype == 'path')
 				return /^(\/)?([^/\0]+(\/)?)+$/;
@@ -1859,7 +1971,7 @@ var QDR = (function (QDR) {
 		$scope.attributeType = '';
 		$scope.attributeRequired = '';
 		$scope.attributeUnique = '';
-		$scope.active = 'container'
+		$scope.active = 'router'
 		$scope.fieldsetDivs = "/fieldsetDivs.html"
 		$scope.setActive = function (tabName) {
 			$scope.active = tabName
@@ -1879,8 +1991,11 @@ var QDR = (function (QDR) {
         // handle the download button click
         // copy the dialog's values to the original node
         $scope.download = function () {
-	        $uibModalInstance.close({entities: $scope.entities, annotations: annotations});
+	        dialog.close({entities: $scope.entities, annotations: annotations});
         }
+        $scope.cancel = function () {
+            dialog.close()
+        };
 
 		$scope.selectAnnotationTab = function (tabName) {
             var tabs = $( "#tabs" ).tabs();
@@ -1902,8 +2017,7 @@ var QDR = (function (QDR) {
 
   });
 
-QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRService, $templateCache, $window, $uibModalInstance, results) {
-
+QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRService, $templateCache, $window, dialog, results) {
 		var result = results.entities;
 		var annotations = results.annotations;
 		var annotationKeys = Object.keys(annotations);
@@ -2002,26 +2116,19 @@ QDR.module.controller("QDR.DownloadDialogController", function($scope, QDRServic
 
         // handle the download button click
         $scope.download = function () {
-			var blob = new Blob([$scope.output], { type: 'text/plain' });
-	        var downloadLink = angular.element('<a></a>');
-	        downloadLink.attr('href', ($window.URL || $window.webkitURL).createObjectURL(blob));
-	        downloadLink.attr('download', $scope.newRouterName);
-	        downloadLink[0].click();
+			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' });
-	        var downloadLink = angular.element('<a></a>');
-	        downloadLink.attr('href', ($window.URL || $window.webkitURL).createObjectURL(blob));
-	        downloadLink.attr('download', linkName);
-	        downloadLink[0].click();
-
-			QDR.log.debug(part);
+			var blob = new Blob([part.output], { type: 'text/plain;charset=utf-16' });
+			saveAs(blob, linkName);
 		}
 
 		$scope.done = function () {
-	        $uibModalInstance.close();
+	        dialog.close();
 		}
 });
 


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


[07/11] qpid-dispatch git commit: DISPATCH-316 Hawtio and stand-alone consoles now use common code

Posted by ea...@apache.org.
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/hawtio/src/main/webapp/plugin/lib/rhea-min.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/lib/rhea-min.js b/console/hawtio/src/main/webapp/plugin/lib/rhea-min.js
deleted file mode 100644
index 4841569..0000000
--- a/console/hawtio/src/main/webapp/plugin/lib/rhea-min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-require=function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){(function(process,Buffer){"use strict";var frames=require("./frames.js");var log=require("./log.js");var sasl=require("./sasl.js");var types=require("./types.js");var util=require("./util.js");var EndpointState=require("./endpoint.js");var Session=require("./session.js");var Transport=require("./transport.js");var net=require("net");var tls=require("tls");var EventEmitter=require("events").EventEmitter;var AMQP_PROTOCOL_ID=0;var TLS_PROTOCOL_ID=2;function ConnectionError(message,condition,connection){Error
 .call(this);Error.captureStackTrace(this,this.constructor);this.message=message;this.condition=condition;this.description=message;this.connection=connection}require("util").inherits(ConnectionError,Error);function get_socket_id(socket){if(socket.get_id_string)return socket.get_id_string();return socket.localAddress+":"+socket.localPort+" -> "+socket.remoteAddress+":"+socket.remotePort}function session_per_connection(conn){var ssn=null;return{get_session:function(){if(!ssn){ssn=conn.create_session();ssn.begin()}return ssn}}}function restrict(count,f){if(count){var current=count;var reset;return function(successful_attempts){if(reset!==successful_attempts){current=count;reset=successful_attempts}if(current--)return f(successful_attempts);else return-1}}else{return f}}function backoff(initial,max){var delay=initial;var reset;return function(successful_attempts){if(reset!==successful_attempts){delay=initial;reset=successful_attempts}var current=delay;var next=delay*2;delay=max>next?next
 :max;return current}}function get_connect_fn(options){if(options.transport===undefined||options.transport==="tcp"){return net.connect}else if(options.transport==="tls"||options.transport==="ssl"){return tls.connect}else{throw Error("Unrecognised transport: "+options.transport)}}function connection_details(options){var details={};details.connect=options.connect?options.connect:get_connect_fn(options);details.host=options.host?options.host:"localhost";details.port=options.port?options.port:5672;details.options=options;return details}var aliases=["container_id","hostname","max_frame_size","channel_max","idle_time_out","outgoing_locales","incoming_locales","offered_capabilities","desired_capabilities","properties"];function remote_property_shortcut(name){return function(){return this.remote.open?this.remote.open[name]:undefined}}var conn_counter=1;var Connection=function(options,container){this.options={};if(options){for(var k in options){this.options[k]=options[k]}}this.container=conta
 iner;if(!this.options.id){this.options.id="connection-"+conn_counter++}if(!this.options.container_id){this.options.container_id=container?container.id:util.generate_uuid()}if(!this.options.connection_details){var self=this;this.options.connection_details=function(){return connection_details(self.options)}}var reconnect=this.get_option("reconnect",true);if(typeof reconnect==="boolean"&&reconnect){var initial=this.get_option("initial_reconnect_delay",100);var max=this.get_option("max_reconnect_delay",6e4);this.options.reconnect=restrict(this.get_option("reconnect_limit"),backoff(initial,max))}else if(typeof reconnect==="number"){var fixed=this.options.reconnect;this.options.reconnect=restrict(this.get_option("reconnect_limit"),function(){return fixed})}this.registered=false;this.state=new EndpointState;this.local_channel_map={};this.remote_channel_map={};this.local={};this.remote={};this.local.open=frames.open(this.options);this.local.close=frames.close({});this.session_policy=session
 _per_connection(this);this.amqp_transport=new Transport(this.options.id,AMQP_PROTOCOL_ID,frames.TYPE_AMQP,this);this.sasl_transport=undefined;this.transport=this.amqp_transport;this.conn_established_counter=0;this.heartbeat_out=undefined;this.heartbeat_in=undefined;this.abort_idle=false;this.socket_ready=false;this.scheduled_reconnect=undefined;this.default_sender=undefined;for(var i in aliases){var f=aliases[i];Object.defineProperty(this,f,{get:remote_property_shortcut(f)})}Object.defineProperty(this,"error",{get:function(){return this.remote.close?this.remote.close.error:undefined}})};Connection.prototype=Object.create(EventEmitter.prototype);Connection.prototype.constructor=Connection;Connection.prototype.dispatch=function(name,context){log.events("Connection got event: "+name);if(this.listeners(name).length){EventEmitter.prototype.emit.apply(this,arguments);return true}else if(this.container){return this.container.dispatch.apply(this.container,arguments)}};Connection.prototype.r
 eset=function(){if(this.abort_idle){this.abort_idle=false;this.local.close.error=undefined;this.state=new EndpointState;this.state.open()}this.amqp_transport=new Transport(this.options.id,AMQP_PROTOCOL_ID,frames.TYPE_AMQP,this);this.sasl_transport=undefined;this.transport=this.amqp_transport;this.state.disconnected();this.remote={};this.remote_channel_map={};for(var k in this.local_channel_map){this.local_channel_map[k].reset()}this.socket_ready=false};Connection.prototype.connect=function(){this.is_server=false;this._connect(this.options.connection_details(this.conn_established_counter));this.open();return this};Connection.prototype.reconnect=function(){this.scheduled_reconnect=undefined;log.reconnect("reconnecting...");this.reset();this._connect(this.options.connection_details(this.conn_established_counter));process.nextTick(this._process.bind(this));return this};Connection.prototype._connect=function(details){if(details.connect){this.init(details.connect(details.port,details.host
 ,details.options,this.connected.bind(this)))}else{this.init(get_connect_fn(details)(details.port,details.host,details.options,this.connected.bind(this)))}return this};Connection.prototype.accept=function(socket){this.is_server=true;log.io("["+this.id+"] client accepted: "+get_socket_id(socket));this.socket_ready=true;return this.init(socket)};Connection.prototype.init=function(socket){this.socket=socket;this.socket.on("data",this.input.bind(this));this.socket.on("error",this.on_error.bind(this));this.socket.on("end",this.eof.bind(this));if(this.is_server){var mechs;if(this.container&&Object.getOwnPropertyNames(this.container.sasl_server_mechanisms).length){mechs=this.container.sasl_server_mechanisms}if(this.socket.encrypted&&this.socket.authorized&&this.get_option("enable_sasl_external",false)){mechs=sasl.server_add_external(mechs?util.clone(mechs):{})}if(mechs){this.sasl_transport=new sasl.Server(this,mechs)}}else{var mechanisms=this.get_option("sasl_mechanisms");if(!mechanisms){va
 r username=this.get_option("username");var password=this.get_option("password");if(username){mechanisms=sasl.client_mechanisms();if(password)mechanisms.enable_plain(username,password);else mechanisms.enable_anonymous(username)}}if(this.socket.encrypted&&this.options.cert&&this.get_option("enable_sasl_external",false)){if(!mechanisms)mechanisms=sasl.client_mechanisms();mechanisms.enable_external()}if(mechanisms){this.sasl_transport=new sasl.Client(this,mechanisms)}}this.transport=this.sasl_transport?this.sasl_transport:this.amqp_transport;return this};Connection.prototype.attach_sender=function(options){return this.session_policy.get_session().attach_sender(options)};Connection.prototype.open_sender=Connection.prototype.attach_sender;Connection.prototype.attach_receiver=function(options){return this.session_policy.get_session().attach_receiver(options)};Connection.prototype.open_receiver=Connection.prototype.attach_receiver;Connection.prototype.get_option=function(name,default_value)
 {if(this.options[name]!==undefined)return this.options[name];else if(this.container)return this.container.get_option(name,default_value);else return default_value};Connection.prototype.send=function(msg){if(this.default_sender===undefined){this.default_sender=this.open_sender({target:{}})}return this.default_sender.send(msg)};Connection.prototype.connected=function(){this.socket_ready=true;this.conn_established_counter++;log.io("["+this.options.id+"] connected "+get_socket_id(this.socket));this.output()};Connection.prototype.sasl_failed=function(text){this.transport_error={condition:"amqp:unauthorized-access",description:text};this._handle_error()};Connection.prototype._handle_error=function(){var error=this.get_error();if(error){var handled=this.dispatch("connection_error",this._context());handled=this.dispatch("connection_close",this._context())||handled;if(!handled){EventEmitter.prototype.emit.call(this.container,"error",new ConnectionError(error.description,error.condition,this)
 )}return true}else{return false}};Connection.prototype.get_error=function(){if(this.transport_error)return this.transport_error;if(this.remote.close&&this.remote.close.error)return this.remote.close.error;return undefined};Connection.prototype.output=function(){if(this.socket&&this.socket_ready){if(this.heartbeat_out)clearTimeout(this.heartbeat_out);this.transport.write(this.socket);if((this.is_closed()&&this.state.has_settled()||this.abort_idle||this.transport_error)&&!this.transport.has_writes_pending()){this.socket.end()}else if(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)}}};Connection.prototype.input=function(buff){if(this.heartbeat_in)clearTimeout(this.heartbeat_in);log.io("["+this.options.id+"] read "+buff.length+" bytes");var buffer;if(this.previous_input){buffer=Buffer.concat([this.previous_input,buff],this.previous_input.length+buff.length);this.previous_input=null}else{buffer=b
 uff}var read=this.transport.read(buffer,this);if(read<buffer.length){this.previous_input=buffer.slice(read)}if(this.local.open.idle_time_out)this.heartbeat_in=setTimeout(this.idle.bind(this),this.local.open.idle_time_out);if(this.transport.has_writes_pending()){this.output()}else if(this.is_closed()&&this.state.has_settled()){this.socket.end()}else if(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)}};Connection.prototype.idle=function(){if(this.is_open()){this.abort_idle=true;this.local.close.error={condition:"amqp:resource-limit-exceeded",description:"max idle time exceeded"};this.close()}};Connection.prototype.on_error=function(e){console.log("["+this.options.id+"] error: "+e);this._disconnected()};Connection.prototype.eof=function(e){this._disconnected()};Connection.prototype._disconnected=function(){if(this.heartbeat_out)clearTimeout(this.heartbeat_out);if(this.heart
 beat_in)clearTimeout(this.heartbeat_in);if(!this.is_closed()&&this.scheduled_reconnect==undefined){if(!this.dispatch("disconnected",this._context())){console.log("["+this.options.id+"] disconnected")}if(!this.is_server&&!this.transport_error&&this.options.reconnect){var delay=this.options.reconnect(this.conn_established_counter);if(delay>=0){log.reconnect("Scheduled reconnect in "+delay+"ms");this.scheduled_reconnect=setTimeout(this.reconnect.bind(this),delay)}}}};Connection.prototype.open=function(){if(this.state.open()){this._register()}};Connection.prototype.close=function(error){if(error)this.local.close.error=error;if(this.state.close()){this._register()}};Connection.prototype.is_open=function(){return this.state.is_open()};Connection.prototype.is_closed=function(){return this.state.is_closed()};Connection.prototype.create_session=function(){var i=0;while(this.local_channel_map[i])i++;var session=new Session(this,i);this.local_channel_map[i]=session;return session};Connection.p
 rototype.on_open=function(frame){if(this.state.remote_opened()){this.remote.open=frame.performative;this.open();this.dispatch("connection_open",this._context())}else{throw Error("Open already received")}};Connection.prototype.on_close=function(frame){if(this.state.remote_closed()){this.remote.close=frame.performative;this.close();if(this.remote.close.error){this._handle_error()}else{this.dispatch("connection_close",this._context())}if(this.heartbeat_out)clearTimeout(this.heartbeat_out)}else{throw Error("Close already received")}};Connection.prototype._register=function(){if(!this.registered){this.registered=true;process.nextTick(this._process.bind(this))}};Connection.prototype._process=function(){this.registered=false;do{if(this.state.need_open()){this._write_open()}for(var k in this.local_channel_map){this.local_channel_map[k]._process()}if(this.state.need_close()){this._write_close()}}while(!this.state.has_settled())};Connection.prototype._write_frame=function(channel,frame,payloa
 d){this.amqp_transport.encode(frames.amqp_frame(channel,frame,payload));this.output()};Connection.prototype._write_open=function(){this._write_frame(0,this.local.open.described())};Connection.prototype._write_close=function(){this._write_frame(0,this.local.close.described())};Connection.prototype.on_begin=function(frame){var session;if(frame.performative.remote_channel===null||frame.performative.remote_channel===undefined){session=this.create_session();session.local.begin.remote_channel=frame.channel}else{session=this.local_channel_map[frame.performative.remote_channel];if(!session)throw Error("Invalid value for remote channel "+frame.performative.remote_channel)}session.on_begin(frame);this.remote_channel_map[frame.channel]=session};Connection.prototype.get_peer_certificate=function(){if(this.socket&&this.socket.getPeerCertificate){return this.socket.getPeerCertificate()}else{return undefined}};Connection.prototype.get_tls_socket=function(){if(this.socket&&(this.options.transport==
 ="tls"||this.options.transport==="ssl")){return this.socket}else{return undefined}};Connection.prototype._context=function(c){var context=c?c:{};context.connection=this;if(this.container)context.container=this.container;return context};function delegate_to_session(name){Connection.prototype["on_"+name]=function(frame){var session=this.remote_channel_map[frame.channel];if(!session){throw Error(name+" received on invalid channel "+frame.channel)}session["on_"+name](frame)}}delegate_to_session("end");delegate_to_session("attach");delegate_to_session("detach");delegate_to_session("transfer");delegate_to_session("disposition");delegate_to_session("flow");module.exports=Connection}).call(this,require("_process"),require("buffer").Buffer)},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./sasl.js":8,"./session.js":9,"./transport.js":11,"./types.js":12,"./util.js":13,_process:25,buffer:19,events:23,net:18,tls:18,util:33}],2:[function(require,module,exports){"use strict";var EndpointState=f
 unction(){this.init()};EndpointState.prototype.init=function(){this.local_open=false;this.remote_open=false;this.open_requests=0;this.close_requests=0;this.initialised=false};EndpointState.prototype.open=function(){this.initialised=true;if(!this.local_open){this.local_open=true;this.open_requests++;return true}else{return false}};EndpointState.prototype.close=function(){if(this.local_open){this.local_open=false;this.close_requests++;return true}else{return false}};EndpointState.prototype.disconnected=function(){var was_open=this.local_open;this.init();if(was_open){this.open()}else{this.close()}};EndpointState.prototype.remote_opened=function(frame){if(!this.remote_open){this.remote_open=true;return true}else{return false}};EndpointState.prototype.remote_closed=function(frame){if(this.remote_open){this.remote_open=false;return true}else{return false}};EndpointState.prototype.is_open=function(){return this.local_open&&this.remote_open};EndpointState.prototype.is_closed=function(){retu
 rn this.initialised&&!this.local_open&&!this.remote_open};EndpointState.prototype.has_settled=function(){return this.open_requests==0&&this.close_requests==0};EndpointState.prototype.need_open=function(){if(this.open_requests>0){this.open_requests--;return true}else{return false}};EndpointState.prototype.need_close=function(){if(this.close_requests>0){this.close_requests--;return true}else{return false}};module.exports=EndpointState},{}],3:[function(require,module,exports){"use strict";var types=require("./types.js");var frames={};var by_descriptor={};frames.read_header=function(buffer){var offset=4;var header={};var name=buffer.toString("ascii",0,offset);if(name!=="AMQP"){throw Error("Invalid protocol header for AMQP "+name)}header.protocol_id=buffer.readUInt8(offset++);header.major=buffer.readUInt8(offset++);header.minor=buffer.readUInt8(offset++);header.revision=buffer.readUInt8(offset++);if(header.major!==1||header.minor!==0){throw Error("Unsupported AMQP version: "+JSON.stringi
 fy(header))}return header};frames.write_header=function(buffer,header){var offset=4;buffer.write("AMQP",0,offset,"ascii");buffer.writeUInt8(header.protocol_id,offset++);buffer.writeUInt8(header.major,offset++);buffer.writeUInt8(header.minor,offset++);buffer.writeUInt8(header.revision,offset++);return 8};frames.TYPE_AMQP=0;frames.TYPE_SASL=1;frames.read_frame=function(buffer){var reader=new types.Reader(buffer);var frame={};frame.size=reader.read_uint(4);if(reader.remaining<frame.size){return null}var doff=reader.read_uint(1);if(doff<2){throw Error("Invalid data offset, must be at least 2 was "+doff)}frame.type=reader.read_uint(1);if(frame.type===frames.TYPE_AMQP){frame.channel=reader.read_uint(2)}else if(frame.type===frames.TYPE_SASL){reader.skip(2)}else{throw Error("Unknown frame type "+frame.type)}if(doff>1){reader.skip(doff*4-8)}if(reader.remaining()){frame.performative=reader.read();var c=by_descriptor[frame.performative.descriptor.value];if(c){frame.performative=new c(frame.per
 formative.value)}if(reader.remaining()){frame.payload=reader.read_bytes(reader.remaining())}}return frame};frames.write_frame=function(frame){var writer=new types.Writer;writer.skip(4);writer.write_uint(2,1);writer.write_uint(frame.type,1);if(frame.type===frames.TYPE_AMQP){writer.write_uint(frame.channel,2)}else if(frame.type===frames.TYPE_SASL){writer.write_uint(0,2)}else{throw Error("Unknown frame type "+frame.type)}if(frame.performative){writer.write(frame.performative);if(frame.payload){writer.write_bytes(frame.payload)}}var buffer=writer.toBuffer();buffer.writeUInt32BE(buffer.length,0);return buffer};frames.amqp_frame=function(channel,performative,payload){return{channel:channel||0,type:frames.TYPE_AMQP,performative:performative,payload:payload}};frames.sasl_frame=function(performative){return{channel:0,type:frames.TYPE_SASL,performative:performative}};function define_frame(type,def){var c=types.define_composite(def);frames[def.name]=c.create;by_descriptor[Number(c.descriptor.n
 umeric).toString(10)]=c;by_descriptor[c.descriptor.symbolic]=c}var open={name:"open",code:16,fields:[{name:"container_id",type:"string",mandatory:true},{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:true},{name:"incoming_locales",type:"symbol",multiple:true},{name:"offered_capabilities",type:"symbol",multiple:true},{name:"desired_capabilities",type:"symbol",multiple:true},{name:"properties",type:"symbolic_map"}]};var begin={name:"begin",code:17,fields:[{name:"remote_channel",type:"ushort"},{name:"next_outgoing_id",type:"uint",mandatory:true},{name:"incoming_window",type:"uint",mandatory:true},{name:"outgoing_window",type:"uint",mandatory:true},{name:"handle_max",type:"uint",default_value:"4294967295"},{name:"offered_capabilities",type:"symbol",multiple:true},{name:"desired_capabilities",type:"symbol",m
 ultiple:true},{name:"properties",type:"symbolic_map"}]};var attach={name:"attach",code:18,fields:[{name:"name",type:"string",mandatory:true},{name:"handle",type:"uint",mandatory:true},{name:"role",type:"boolean",mandatory:true},{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:false},{name:"initial_delivery_count",type:"uint"},{name:"max_message_size",type:"ulong"},{name:"offered_capabilities",type:"symbol",multiple:true},{name:"desired_capabilities",type:"symbol",multiple:true},{name:"properties",type:"symbolic_map"}]};var flow={name:"flow",code:19,fields:[{name:"next_incoming_id",type:"uint"},{name:"incoming_window",type:"uint",mandatory:true},{name:"next_outgoing_id",type:"uint",mandatory:true},{name:"outgoing_window",type:"uint",mandatory:true},{name:"handle",type:"uint"},{name:"deliver
 y_count",type:"uint"},{name:"link_credit",type:"uint"},{name:"available",type:"uint"},{name:"drain",type:"boolean",default_value:false},{name:"echo",type:"boolean",default_value:false},{name:"properties",type:"symbolic_map"}]};var transfer={name:"transfer",code:20,fields:[{name:"handle",type:"uint",mandatory:true},{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:false},{name:"rcv_settle_mode",type:"ubyte"},{name:"state",type:"delivery_state"},{name:"resume",type:"boolean",default_value:false},{name:"aborted",type:"boolean",default_value:false},{name:"batchable",type:"boolean",default_value:false}]};var disposition={name:"disposition",code:21,fields:[{name:"role",type:"boolean",mandatory:true},{name:"first",type:"uint",mandatory:true},{name:"last",type:"uint"},{name:"settled",type:"boolean",default_value:false},{name:"state",type:"*"},{name:"batchable",typ
 e:"boolean",default_value:false}]};var detach={name:"detach",code:22,fields:[{name:"handle",type:"uint",mandatory:true},{name:"closed",type:"boolean",default_value:false},{name:"error",type:"error"}]};var end={name:"end",code:23,fields:[{name:"error",type:"error"}]};var close={name:"close",code:24,fields:[{name:"error",type:"error"}]};define_frame(frames.TYPE_AMQP,open);define_frame(frames.TYPE_AMQP,begin);define_frame(frames.TYPE_AMQP,attach);define_frame(frames.TYPE_AMQP,flow);define_frame(frames.TYPE_AMQP,transfer);define_frame(frames.TYPE_AMQP,disposition);define_frame(frames.TYPE_AMQP,detach);define_frame(frames.TYPE_AMQP,end);define_frame(frames.TYPE_AMQP,close);var sasl_mechanisms={name:"sasl_mechanisms",code:64,fields:[{name:"sasl_server_mechanisms",type:"symbol",multiple:true,mandatory:true}]};var sasl_init={name:"sasl_init",code:65,fields:[{name:"mechanism",type:"symbol",mandatory:true},{name:"initial_response",type:"binary"},{name:"hostname",type:"string"}]};var sasl_chal
 lenge={name:"sasl_challenge",code:66,fields:[{name:"challenge",type:"binary",mandatory:true}]};var sasl_response={name:"sasl_response",code:67,fields:[{name:"response",type:"binary",mandatory:true}]};var sasl_outcome={name:"sasl_outcome",code:68,fields:[{name:"code",type:"ubyte",mandatory:true},{name:"additional_data",type:"binary"}]};define_frame(frames.TYPE_SASL,sasl_mechanisms);define_frame(frames.TYPE_SASL,sasl_init);define_frame(frames.TYPE_SASL,sasl_challenge);define_frame(frames.TYPE_SASL,sasl_response);define_frame(frames.TYPE_SASL,sasl_outcome);module.exports=frames},{"./types.js":12}],4:[function(require,module,exports){(function(Buffer){"use strict";var frames=require("./frames.js");var log=require("./log.js");var message=require("./message.js");var terminus=require("./terminus.js");var types=require("./types.js");var EndpointState=require("./endpoint.js");var FlowController=function(window){this.window=window};FlowController.prototype.update=function(context){var delta=t
 his.window-context.receiver.credit;context.receiver.flow(delta)};function auto_settle(context){context.delivery.settled=true}function auto_accept(context){context.delivery.update(undefined,message.accepted().described())}function LinkError(message,condition,link){Error.call(this);Error.captureStackTrace(this,this.constructor);this.message=message;this.condition=condition;this.description=message;this.link=link}require("util").inherits(LinkError,Error);var EventEmitter=require("events").EventEmitter;var link=Object.create(EventEmitter.prototype);link.dispatch=function(name,context){log.events("Link got event: "+name);EventEmitter.prototype.emit.apply(this.observers,arguments);if(this.listeners(name).length){EventEmitter.prototype.emit.apply(this,arguments);return true}else{return this.session.dispatch.apply(this.session,arguments)}};link.set_source=function(fields){this.local.attach.source=terminus.source(fields).described()};link.set_target=function(fields){this.local.attach.target=
 terminus.target(fields).described()};link.attach=function(){if(this.state.open()){this.connection._register()}};link.open=link.attach;link.detach=function(){this.local.detach.closed=false;if(this.state.close()){this.connection._register()}};link.close=function(error){if(error)this.local.detach.error=error;this.local.detach.closed=true;if(this.state.close()){this.connection._register()}};link.is_open=function(){return this.session.is_open()&&this.state.is_open()};link.is_closed=function(){return this.session.is_closed()||this.state.is_closed()};link._process=function(){do{if(this.state.need_open()){this.session.output(this.local.attach.described())}if(this.issue_flow){this.session._write_flow(this);this.issue_flow=false}if(this.state.need_close()){this.session.output(this.local.detach.described())}}while(!this.state.has_settled())};link.on_attach=function(frame){if(this.state.remote_opened()){if(!this.remote.handle){this.remote.handle=frame.handle}frame.performative.source=terminus.u
 nwrap(frame.performative.source);frame.performative.target=terminus.unwrap(frame.performative.target);this.remote.attach=frame.performative;this.open();this.dispatch(this.is_receiver()?"receiver_open":"sender_open",this._context())}else{throw Error("Attach already received")}};link.prefix_event=function(event){return(this.local.attach.role?"receiver_":"sender_")+event};link.on_detach=function(frame){if(this.state.remote_closed()){this.remote.detach=frame.performative;this.close();var error=this.remote.detach.error;if(error){var handled=this.dispatch(this.prefix_event("error"),this._context());handled=this.dispatch(this.prefix_event("close"),this._context())||handled;if(!handled){EventEmitter.prototype.emit.call(this.connection.container,"error",new LinkError(error.description,error.condition,this))}}else{this.dispatch(this.prefix_event("close"),this._context())}}else{throw Error("Detach already received")}};function is_internal(name){switch(name){case"name":case"handle":case"role":c
 ase"initial_delivery_count":return true;default:return false}}var aliases=["snd_settle_mode","rcv_settle_mode","source","target","max_message_size","offered_capabilities","desired_capabilities","properties"];function remote_property_shortcut(name){return function(){return this.remote.attach?this.remote.attach[name]:undefined}}link.init=function(session,name,local_handle,opts,is_receiver){this.session=session;this.connection=session.connection;this.name=name;this.options=opts===undefined?{}:opts;this.state=new EndpointState;this.issue_flow=false;this.local={handle:local_handle};this.local.attach=frames.attach({handle:local_handle,name:name,role:is_receiver});for(var f in this.local.attach){if(!is_internal(f)&&this.options[f]!==undefined){this.local.attach[f]=this.options[f]}}this.local.detach=frames.detach({handle:local_handle,closed:true});this.remote={handle:undefined};this.delivery_count=0;this.credit=0;this.observers=new EventEmitter;for(var i in aliases){var f=aliases[i];Object.
 defineProperty(this,f,{get:remote_property_shortcut(f)})}Object.defineProperty(this,"error",{get:function(){return this.remote.detach?this.remote.detach.error:undefined}})};link.reset=function(){this.state.disconnected();this.remote={handle:undefined};this.delivery_count=0;this.credit=0};link.has_credit=function(){return this.credit>0};link.is_receiver=function(){return this.local.attach.role};link._context=function(c){var context=c?c:{};if(this.is_receiver()){context.receiver=this}else{context.sender=this}return this.session._context(context)};link.get_option=function(name,default_value){if(this.options[name]!==undefined)return this.options[name];else return this.session.get_option(name,default_value)};var Sender=function(session,name,local_handle,opts){this.init(session,name,local_handle,opts,false);this._draining=false;this._drained=false;this.local.attach.initial_delivery_count=0;this.tag=0;if(this.get_option("autosettle",true)){this.observers.on("settled",auto_settle)}var sende
 r=this;if(this.get_option("treat_modified_as_released",true)){this.observers.on("modified",function(context){sender.dispatch("released",context)})}};Sender.prototype=Object.create(link);Sender.prototype.constructor=Sender;Sender.prototype._get_drain=function(){if(this._draining&&this._drained&&this.credit){while(this.credit){++this.delivery_count;--this.credit}return true}else{return false}};Sender.prototype.set_drained=function(drained){this._drained=drained;if(this._draining&&this._drained){this.issue_flow=true}};Sender.prototype.next_tag=function(){return new Buffer(new String((this.tag++)))};Sender.prototype.sendable=function(frame){return this.credit&&this.session.outgoing.available()};Sender.prototype.on_flow=function(frame){var flow=frame.performative;this.credit=flow.delivery_count+flow.link_credit-this.delivery_count;this._draining=flow.drain;this._drained=this.credit>0;if(this.is_open()){this.dispatch("sender_flow",this._context());if(this._draining){this.dispatch("sender_
 draining",this._context())}if(this.sendable()){this.dispatch("sendable",this._context())}}};Sender.prototype.on_transfer=function(frame){throw Error("got transfer on sending link")};Sender.prototype.send=function(msg,tag){var delivery=this.session.send(this,tag?tag:this.next_tag(),message.encode(msg),0);if(this.local.attach.snd_settle_mode===1){delivery.settled=true}return delivery};var Receiver=function(session,name,local_handle,opts){this.init(session,name,local_handle,opts,true);this.drain=false;this.set_credit_window(this.get_option("credit_window",100));if(this.get_option("autoaccept",true)){this.observers.on("message",auto_accept)}};Receiver.prototype=Object.create(link);Receiver.prototype.constructor=Receiver;Receiver.prototype.on_flow=function(frame){this.dispatch("receiver_flow",this._context());if(frame.performative.drain){if(frame.performative.link_credit>0)console.log("ERROR: received flow with drain set, but non zero credit");else this.dispatch("receiver_drained",this._
 context())}};Receiver.prototype.flow=function(credit){if(credit>0){this.credit+=credit;this.issue_flow=true;this.connection._register()}};Receiver.prototype.add_credit=Receiver.prototype.flow;Receiver.prototype._get_drain=function(){return this.drain};Receiver.prototype.set_credit_window=function(credit_window){if(credit_window>0){var flow_controller=new FlowController(credit_window);var listener=flow_controller.update.bind(flow_controller);this.observers.on("message",listener);this.observers.on("receiver_open",listener)}};module.exports={Sender:Sender,Receiver:Receiver}}).call(this,require("buffer").Buffer)},{"./endpoint.js":2,"./frames.js":3,"./log.js":5,"./message.js":6,"./terminus.js":10,"./types.js":12,buffer:19,events:23,util:33}],5:[function(require,module,exports){"use strict";var debug=require("debug");module.exports={frames:debug("rhea:frames"),raw:debug("rhea:raw"),reconnect:debug("rhea:reconnect"),events:debug("rhea:events"),message:debug("rhea:message"),flow:debug("rhea
 :flow"),io:debug("rhea:io")}},{debug:15}],6:[function(require,module,exports){"use strict";var log=require("./log.js");
-var types=require("./types.js");var by_descriptor={};var unwrappers={};var wrappers=[];var message={};function define_section(descriptor,unwrap,wrap){unwrap.descriptor=descriptor;unwrappers[descriptor.symbolic]=unwrap;unwrappers[Number(descriptor.numeric).toString(10)]=unwrap;if(wrap){wrappers.push(wrap)}}function define_composite_section(def){var c=types.define_composite(def);message[def.name]=c.create;by_descriptor[Number(c.descriptor.numeric).toString(10)]=c;by_descriptor[c.descriptor.symbolic]=c;var unwrap=function(msg,section){msg[def.name]=new c(section.value)};var wrap=function(sections,msg){if(msg[def.name]){if(msg[def.name].described){sections.push(msg[def.name].described())}else{sections.push(c.create(msg[def.name]).described())}}};define_section(c.descriptor,unwrap,wrap)}function define_map_section(def){var descriptor={numeric:def.code};descriptor.symbolic="amqp:"+def.name.replace(/_/g,"-")+":map";var unwrap=function(msg,section){msg[def.name]=types.unwrap(section)};var w
 rap=function(sections,msg){if(msg[def.name]){sections.push(types.described(types.wrap_ulong(descriptor.numeric),types.wrap_map(msg[def.name])))}};define_section(descriptor,unwrap,wrap)}define_composite_section({name:"header",code:112,fields:[{name:"durable",type:"boolean",default_value:false},{name:"priority",type:"ubyte",default_value:4},{name:"ttl",type:"uint"},{name:"first_acquirer",type:"boolean",default_value:false},{name:"delivery_count",type:"uint",default_value:0}]});define_map_section({name:"delivery_annotations",code:113});define_map_section({name:"message_annotations",code:114});define_composite_section({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"}]});define_map_section({name:"application_properties",code:116});define_section({numeric:119,symbolic:"amqp:value:*"},function(msg,section){msg.body=types.unwrap(section)},function(sections,msg){sections.push(types.described(types.wrap_ulong(119),types.wrap(msg.body)))});define_map_section({name:"footer",code:120});function reverse_lookup(map){var reversed={};for(var key in map){var values=map[key];for(var i in values){reversed[values[i]]=key}}return reversed}var special_sections={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"]};var special_fields=reverse_lookup(special_sections);var section_names=["header","delivery_annotations","mess
 age_annotations","properties","application_properties","body","footer"];function copy(src,tgt){for(var k in src){var v=src[k];if(typeof v==="object"){copy(v,tgt[k])}else{tgt[k]=v}}}function add_property_shortcut(o,group,name){Object.defineProperty(o,name,{get:function(){return this[group]?this[group][name]:undefined},set:function(v){if(this[group]===undefined){this[group]={}}this[group][name]=v}})}function create_message(m){var msg={toJSON:function(){var o={};for(var i in section_names){var key=section_names[i];if(typeof this[key]==="function")continue;var fields=special_sections[key];if(fields){for(var j in fields){if(this[fields[j]]!==undefined){if(o[key]===undefined)o[key]={};o[key][fields[j]]=this[fields[j]]}}}else if(this[key]){o[key]=this[key]}}return o},inspect:function(){return JSON.stringify(this.toJSON())},toString:function(){return JSON.stringify(this.toJSON())}};for(var field in special_fields){add_property_shortcut(msg,special_fields[field],field)}if(m)copy(m,msg);retur
 n msg}function massage(msg){var out=undefined;for(var key in msg){if(typeof msg[key]!=="function"&&section_names.indexOf(key)<0){if(out===undefined)out={};var group=special_fields[key]||"application_properties";if(out[group]===undefined){out[group]={}}out[group][key]=msg[key]}else{if(out!==undefined){out[key]=msg[key]}}}return out||msg}message.encode=function(obj){var sections=[];var msg=massage(obj);wrappers.forEach(function(wrapper_fn){wrapper_fn(sections,msg)});var writer=new types.Writer;for(var i=0;i<sections.length;i++){log.message("Encoding section "+(i+1)+" of "+sections.length+": "+sections[i]);writer.write(sections[i])}var data=writer.toBuffer();log.message("encoded "+data.length+" bytes");return data};message.decode=function(buffer){var msg=create_message();var reader=new types.Reader(buffer);while(reader.remaining()){var s=reader.read();log.message("decoding section: "+JSON.stringify(s)+" of type: "+JSON.stringify(s.descriptor));if(s.descriptor){var unwrap=unwrappers[s.d
 escriptor.value];if(unwrap){unwrap(msg,s)}else{console.log("WARNING: did not recognise message section with descriptor "+s.descriptor)}}else{console.log("WARNING: expected described message section got "+JSON.stringify(s))}}if(msg.application_properties){for(var key in msg.application_properties){add_property_shortcut(msg,"application_properties",key)}}return msg};var outcomes={};function define_outcome(def){var c=types.define_composite(def);c.composite_type=def.name;message[def.name]=c.create;outcomes[Number(c.descriptor.numeric).toString(10)]=c;outcomes[c.descriptor.symbolic]=c;message["is_"+def.name]=function(o){if(o&&o.descriptor){var c=outcomes[o.descriptor.value];if(c){return c.descriptor.numeric==def.code}}return false}}message.unwrap_outcome=function(outcome){if(outcome&&outcome.descriptor){var c=outcomes[outcome.descriptor.value];if(c){return new c(types.unwrap(outcome))}}console.log("unrecognised outcome: "+JSON.stringify(outcome));return outcome};message.are_outcomes_equi
 valent=function(a,b){if(a===undefined&&b===undefined)return true;else if(a===undefined||b===undefined)return false;else return a.descriptor.value==b.descriptor.value&&JSON.stringify(a)==JSON.stringify(b)};define_outcome({name:"received",code:35,fields:[{name:"section_number",type:"uint",mandatory:true},{name:"section_offset",type:"ulong",mandatory:true}]});define_outcome({name:"accepted",code:36,fields:[]});define_outcome({name:"rejected",code:37,fields:[{name:"error",type:"error"}]});define_outcome({name:"released",code:38,fields:[]});define_outcome({name:"modified",code:39,fields:[{name:"delivery_failed",type:"boolean"},{name:"undeliverable_here",type:"boolean"},{name:"message_annotations",type:"map"}]});module.exports=message},{"./log.js":5,"./types.js":12}],7:[function(require,module,exports){"use strict";var url=require("url");var simple_id_generator={counter:1,next:function(){return this.counter++}};var Client=function(container,address){var u=url.parse(address);this.connectio
 n=container.connect({host:u.hostname,port:u.port});this.connection.on("message",this._response.bind(this));this.connection.on("receiver_open",this._ready.bind(this));this.sender=this.connection.attach_sender(u.path.substr(1));this.receiver=this.connection.attach_receiver({source:{dynamic:true}});this.id_generator=simple_id_generator;this.pending=[];this.outstanding={}};Client.prototype._request=function(id,name,args,callback){var request={properties:{}};request.properties.subject=name;request.body=args;request.properties.message_id=id;request.properties.reply_to=this.receiver.remote.attach.source.address;this.outstanding[id]=callback;this.sender.send(request)};Client.prototype._response=function(context){var id=context.message.properties.correlation_id;var callback=this.outstanding[id];if(callback){if(context.message.properties.subject==="ok"){callback(context.message.body)}else{callback(undefined,{name:context.message.properties.subject,description:context.message.body})}}else{cons
 ole.log("no request pending for "+id+", ignoring response")}};Client.prototype._ready=function(context){this._process_pending()};Client.prototype._process_pending=function(){for(var i=0;i<this.pending.length;i++){var r=this.pending[i];this._request(r.id,r.name,r.args,r.callback)}this.pending=[]};Client.prototype.call=function(name,args,callback){var id=this.id_generator.next();if(this.receiver.is_open()&&this.pending.length===0){this._request(id,name,args,callback)}else{this.pending.push({name:name,args:args,callback:callback,id:id})}};Client.prototype.close=function(){this.receiver.close();this.sender.close();this.connection.close()};Client.prototype.define=function(name){this[name]=function(args,callback){this.call(name,args,callback)}};var Cache=function(ttl,purged){this.ttl=ttl;this.purged=purged;this.entries={};this.timeout=undefined};Cache.prototype.clear=function(){if(this.timeout)clearTimeout(this.timeout);this.entries={}};Cache.prototype.put=function(key,value){this.entries
 [key]={value:value,last_accessed:Date.now()};if(!this.timeout)this.timeout=setTimeout(this.purge.bind(this),this.ttl)};Cache.prototype.get=function(key){var entry=this.entries[key];if(entry){entry.last_accessed=Date.now();return entry.value}else{return undefined}};Cache.prototype.purge=function(){var now=Date.now();var expired=[];var live=0;for(var k in this.entries){if(now-this.entries[k].last_accessed>=this.ttl){expired.push(k)}else{live++}}for(var i=0;i<expired.length;i++){var entry=this.entries[expired[i]];delete this.entries[expired[i]];this.purged(entry.value)}if(live&&!this.timeout){this.timeout=setTimeout(this.purge.bind(this),this.ttl)}};var LinkCache=function(factory,ttl){this.factory=factory;this.cache=new Cache(ttl,function(link){link.close()})};LinkCache.prototype.clear=function(){this.cache.clear()};LinkCache.prototype.get=function(address){var link=this.cache.get(address);if(link===undefined){link=this.factory(address);this.cache.put(address,link)}return link};var Ser
 ver=function(container,address,options){this.options=options||{};var u=url.parse(address);this.connection=container.connect({host:u.hostname,port:u.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(u.path.substr(1));this.callbacks={};this._send=undefined;this._clear=undefined};function match(desired,offered){if(offered){if(Array.isArray(offered)){return offered.indexOf(desired)>-1}else{return desired===offered}}else{return false}}Server.prototype._connection_open=function(context){if(match("ANONYMOUS-RELAY",this.connection.remote.open.offered_capabilities)){var relay=this.connection.attach_sender({target:{}});this._send=function(msg){relay.send(msg)}}else{var cache=new LinkCache(this.connection.attach_sender.bind(this.connection),this.options.cache_ttl||6e4);this._send=function(msg){var s=cache.get(msg.properties.to);if(s)s.send(msg)};this._clear=function(
 ){cache.clear()}}};Server.prototype._respond=function(response){var server=this;return function(result,error){if(error){response.properties.subject=error.name||"error";response.body=error.description||error}else{response.properties.subject="ok";response.body=result}server._send(response)}};Server.prototype._request=function(context){var request=context.message;var response={properties:{}};response.properties.to=request.properties.reply_to;response.properties.correlation_id=request.properties.message_id;var callback=this.callbacks[request.properties.subject];if(callback){callback(request.body,this._respond(response))}else{response.properties.subject="bad-method";response.body="Unrecognised method "+request.properties.subject;this._send(response)}};Server.prototype.bind_sync=function(f,name){this.callbacks[name||f.name]=function(args,callback){var result=f(args);callback(result)}};Server.prototype.bind=function(f,name){this.callbacks[name||f.name]=f};Server.prototype.close=function(){
 if(this._clear)this._clear();this.receiver.close();this.connection.close()};module.exports={server:function(container,address,options){return new Server(container,address,options)},client:function(connection,address){return new Client(connection,address)}}},{url:30}],8:[function(require,module,exports){(function(Buffer){"use strict";var frames=require("./frames.js");var log=require("./log.js");var Transport=require("./transport.js");var sasl_codes={OK:0,AUTH:1,SYS:2,SYS_PERM:3,SYS_TEMP:4};var SASL_PROTOCOL_ID=3;function intersection(lista,listb){return lista.filter(function(a){return listb.indexOf(a)>=0})}function extract(buffer){var results=[];var start=0;var i=0;while(i<buffer.length){if(buffer[i]===0){if(i>start)results.push(buffer.toString("utf8",start,i));else results.push(null);start=++i}else{++i}}if(i>start)results.push(buffer.toString("utf8",start,i));else results.push(null);return results}var PlainServer=function(callback){this.callback=callback;this.outcome=undefined;this.
 username=undefined};PlainServer.prototype.start=function(response){var fields=extract(response);if(fields.length!==3){this.connection.sasl_failed("Unexpected response in PLAIN, got "+fields.length+" fields, expected 3")}if(this.callback(fields[1],fields[2])){this.outcome=true;this.username=fields[1]}else{this.outcome=false}};var PlainClient=function(username,password){this.username=username;this.password=password};PlainClient.prototype.start=function(){var response=new Buffer(1+this.username.length+1+this.password.length);response.writeUInt8(0,0);response.write(this.username,1);response.writeUInt8(0,1+this.username.length);response.write(this.password,1+this.username.length+1);return response};var AnonymousServer=function(){this.outcome=undefined;this.username=undefined};AnonymousServer.prototype.start=function(response){this.outcome=true;this.username=response?response.toString("utf8"):"anonymous"};var AnonymousClient=function(name){this.username=name?name:"anonymous"};AnonymousCli
 ent.prototype.start=function(){var response=new Buffer(1+this.username.length);response.writeUInt8(0,0);response.write(this.username,1);return response};var ExternalServer=function(){this.outcome=undefined;this.username=undefined};ExternalServer.prototype.start=function(response){this.outcome=true};var ExternalClient=function(){this.username=undefined};ExternalClient.prototype.start=function(){return null};var SaslServer=function(connection,mechanisms){this.connection=connection;this.transport=new Transport(connection.amqp_transport.identifier,SASL_PROTOCOL_ID,frames.TYPE_SASL,this);this.next=connection.amqp_transport;this.mechanisms=mechanisms;this.mechanism=undefined;this.outcome=undefined;this.username=undefined;var mechlist=Object.getOwnPropertyNames(mechanisms);this.transport.encode(frames.sasl_frame(frames.sasl_mechanisms({sasl_server_mechanisms:mechlist}).described()))};SaslServer.prototype.do_step=function(challenge){if(this.mechanism.outcome===undefined){this.transport.enco
 de(frames.sasl_frame(frames.sasl_challenge({challenge:challenge}).described()))}else{this.outcome=this.mechanism.outcome?sasl_codes.OK:sasl_codes.AUTH;this.transport.encode(frames.sasl_frame(frames.sasl_outcome({code:this.outcome}).described()));if(this.outcome===sasl_codes.OK){this.username=this.mechanism.username;this.transport.write_complete=true;this.transport.read_complete=true}}};SaslServer.prototype.on_sasl_init=function(frame){var f=this.mechanisms[frame.performative.mechanism];if(f){this.mechanism=f();var challenge=this.mechanism.start(frame.performative.initial_response);this.do_step(challenge)}else{this.outcome=sasl_codes.AUTH;this.transport.encode(frames.sasl_frame(frames.sasl_outcome({code:this.outcome}).described()))}};SaslServer.prototype.on_sasl_response=function(frame){this.do_step(this.mechanism.step(frame.performative.response))};SaslServer.prototype.has_writes_pending=function(){return this.transport.has_writes_pending()||this.next.has_writes_pending()};SaslServe
 r.prototype.write=function(socket){if(this.transport.write_complete&&this.transport.pending.length===0){return this.next.write(socket)}else{return this.transport.write(socket)}};SaslServer.prototype.read=function(buffer){if(this.transport.read_complete){return this.next.read(buffer)}else{return this.transport.read(buffer)}};var SaslClient=function(connection,mechanisms){this.connection=connection;this.transport=new Transport(connection.amqp_transport.identifier,SASL_PROTOCOL_ID,frames.TYPE_SASL,this);this.next=connection.amqp_transport;this.mechanisms=mechanisms;this.mechanism=undefined;this.mechanism_name=undefined;this.failed=false};SaslClient.prototype.on_sasl_mechanisms=function(frame){for(var i=0;this.mechanism===undefined&&i<frame.performative.sasl_server_mechanisms.length;i++){var mech=frame.performative.sasl_server_mechanisms[i];var f=this.mechanisms[mech];if(f){this.mechanism=f();this.mechanism_name=mech}}if(this.mechanism){var response=this.mechanism.start();this.transport
 .encode(frames.sasl_frame(frames.sasl_init({mechanism:this.mechanism_name,initial_response:response}).described()))}else{this.failed=true;this.connection.sasl_failed("No suitable mechanism; server supports "+frame.performative.sasl_server_mechanisms)}};SaslClient.prototype.on_sasl_challenge=function(frame){var response=this.mechanism.step(frame.performative.challenge);this.transport.encode(frames.sasl_frame(frames.sasl_response({response:response}).described()))};SaslClient.prototype.on_sasl_outcome=function(frame){switch(frame.performative.code){case sasl_codes.OK:this.transport.read_complete=true;this.transport.write_complete=true;break;default:this.transport.write_complete=true;this.connection.sasl_failed("Failed to authenticate: "+frame.performative.code)}};SaslClient.prototype.has_writes_pending=function(){return this.transport.has_writes_pending()||this.next.has_writes_pending()};SaslClient.prototype.write=function(socket){if(this.transport.write_complete){return this.next.wri
 te(socket)}else{return this.transport.write(socket)}};SaslClient.prototype.read=function(buffer){if(this.transport.read_complete){return this.next.read(buffer)}else{return this.transport.read(buffer)}};var default_server_mechanisms={enable_anonymous:function(){this["ANONYMOUS"]=function(){return new AnonymousServer}},enable_plain:function(callback){this["PLAIN"]=function(){return new PlainServer(callback)}}};var default_client_mechanisms={enable_anonymous:function(name){this["ANONYMOUS"]=function(){return new AnonymousClient(name)}},enable_plain:function(username,password){this["PLAIN"]=function(){return new PlainClient(username,password)}},enable_external:function(){this["EXTERNAL"]=function(){return new ExternalClient}}};module.exports={Client:SaslClient,Server:SaslServer,server_mechanisms:function(){return Object.create(default_server_mechanisms)},client_mechanisms:function(){return Object.create(default_client_mechanisms)},server_add_external:function(mechs){mechs["EXTERNAL"]=fu
 nction(){return new ExternalServer};return mechs}}}).call(this,require("buffer").Buffer)},{"./frames.js":3,"./log.js":5,"./transport.js":11,buffer:19}],9:[function(require,module,exports){(function(Buffer){"use strict";var frames=require("./frames.js");var link=require("./link.js");var log=require("./log.js");var message=require("./message.js");var types=require("./types.js");var util=require("./util.js");var EndpointState=require("./endpoint.js");var EventEmitter=require("events").EventEmitter;var CircularBuffer=function(capacity){this.capacity=capacity;this.size=0;this.head=0;this.tail=0;this.entries=[]};CircularBuffer.prototype.available=function(){return this.capacity-this.size};CircularBuffer.prototype.push=function(o){if(this.size<this.capacity){this.entries[this.tail]=o;this.tail=(this.tail+1)%this.capacity;this.size++}else{throw Error("circular buffer overflow: head="+this.head+" tail="+this.tail+" size="+this.size+" capacity="+this.capacity)}};CircularBuffer.prototype.pop_i
 f=function(f){var count=0;while(this.size&&f(this.entries[this.head])){this.entries[this.head]=undefined;this.head=(this.head+1)%this.capacity;this.size--;count++}return count};CircularBuffer.prototype.by_id=function(id){if(this.size>0){var gap=id-this.entries[this.head].id;if(gap<this.size){return this.entries[(this.head+gap)%this.capacity]}}return undefined};CircularBuffer.prototype.get_head=function(id){return this.size>0?this.entries[this.head]:undefined};function write_dispositions(deliveries){var first;var last;var next_id;for(var i=0;i<deliveries.length;i++){var delivery=deliveries[i];if(first===undefined){first=delivery;last=delivery;next_id=delivery.id}if(!message.are_outcomes_equivalent(last.state,delivery.state)||last.settled!==delivery.settled||next_id!==delivery.id){first.link.session.output(frames.disposition({role:first.link.is_receiver(),first:first.id,last:last.id,state:first.state,settled:first.settled}).described());first=delivery;last=delivery;next_id=delivery.id
 }else{if(last.id!==delivery.id){last=delivery}next_id++}}if(first!==undefined&&last!==undefined){first.link.session.output(frames.disposition({role:first.link.is_receiver(),first:first.id,last:last.id,state:first.state,settled:first.settled}).described())}}var Outgoing=function(){this.deliveries=new CircularBuffer(2048);this.updated=[];this.pending_dispositions=[];this.next_delivery_id=0;this.next_pending_delivery=0;this.next_transfer_id=0;this.window=types.MAX_UINT;this.remote_next_transfer_id=undefined;this.remote_window=undefined};Outgoing.prototype.available=function(){return this.deliveries.available()};Outgoing.prototype.send=function(sender,tag,data,format){var d={id:this.next_delivery_id++,tag:tag,link:sender,data:data,format:format?format:0,sent:false,settled:false,state:undefined,remote_settled:false,remote_state:undefined};var self=this;d.update=function(settled,state){self.update(d,settled,state)};this.deliveries.push(d);return d};Outgoing.prototype.on_begin=function(fie
 lds){this.remote_window=fields.incoming_window};Outgoing.prototype.on_flow=function(fields){this.remote_next_transfer_id=fields.next_incoming_id;this.remote_window=fields.incoming_window};Outgoing.prototype.on_disposition=function(fields){var last=fields.last?fields.last:fields.first;for(var i=fields.first;i<=last;i++){var d=this.deliveries.by_id(i);if(d&&!d.remote_settled){var updated=false;if(fields.settled){d.remote_settled=fields.settled;updated=true}if(fields.state&&fields.state!==d.remote_state){d.remote_state=message.unwrap_outcome(fields.state);updated=true}if(updated){this.updated.push(d)}}}};Outgoing.prototype.update=function(delivery,settled,state){if(delivery){delivery.settled=settled;if(state!==undefined)delivery.state=state;if(!delivery.remote_settled){this.pending_dispositions.push(delivery)}delivery.link.connection._register()}};Outgoing.prototype.transfer_window=function(){if(this.remote_window){return this.remote_window-(this.next_transfer_id-this.remote_next_trans
 fer_id)}};Outgoing.prototype.process=function(){while(this.next_pending_delivery<this.next_delivery_id){var d=this.deliveries.by_id(this.next_pending_delivery);if(d){if(d.link.has_credit()){d.link.delivery_count++;d.transfers_required=1;if(this.transfer_window()>=d.transfers_required){this.next_transfer_id+=d.transfers_required;this.window-=d.transfers_required;d.link.session.output(frames.transfer({handle:d.link.local.handle,message_format:d.format,delivery_id:d.id,delivery_tag:d.tag,settled:d.settled}).described(),d.data);d.link.credit--;this.next_pending_delivery++}else{log.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}}else{log.flow("Link has no credit");break}}else{console.log("ERROR: Next pending delivery not found: "+this.next_pending_delivery);break}}for(var i=0;i<this.updated.length;i++){var d=this.updated[i];i
 f(d.remote_state&&d.remote_state.constructor.composite_type){d.link.dispatch(d.remote_state.constructor.composite_type,d.link._context({delivery:d}))}if(d.remote_settled)d.link.dispatch("settled",d.link._context({delivery:d}))}this.updated=[];if(this.pending_dispositions.length){write_dispositions(this.pending_dispositions);this.pending_dispositions=[]}this.deliveries.pop_if(function(d){return d.settled&&d.remote_settled})};var Incoming=function(){this.deliveries=new CircularBuffer(2048);this.updated=[];this.next_transfer_id=0;this.next_delivery_id=undefined;this.window=2048;this.remote_next_transfer_id=undefined;this.remote_window=undefined};Incoming.prototype.update=function(delivery,settled,state){if(delivery){delivery.settled=settled;if(state!==undefined)delivery.state=state;if(!delivery.remote_settled){this.updated.push(delivery)}delivery.link.connection._register()}};Incoming.prototype.on_transfer=function(frame,receiver){this.next_transfer_id++;if(receiver.is_open()){if(this.
 next_delivery_id===undefined){this.next_delivery_id=frame.performative.delivery_id}var current;var data;var last=this.deliveries.get_head();if(last&&last.incomplete){if(frame.performative.delivery_id!==undefined&&this.next_delivery_id!=frame.performative.delivery_id){throw Error("frame sequence error: delivery "+this.next_delivery_id+" not complete, got "+frame.performative.delivery_id)}current=last;data=Buffer.concat([current.data,frame.payload],current.data.size()+frame.payload.size())}else if(this.next_delivery_id===frame.performative.delivery_id){current={id:frame.performative.delivery_id,tag:frame.performative.delivery_tag,link:receiver,settled:false,state:undefined,remote_settled:frame.performative.settled===undefined?false:frame.performative.settled,remote_state:frame.performative.state};var self=this;current.update=function(settled,state){var settled_=settled;if(settled_===undefined){settled_=receiver.local.attach.rcv_settle_mode!==1}self.update(current,settled_,state)};curr
 ent.accept=function(){this.update(undefined,message.accepted().described())};current.release=function(params){if(params){this.update(undefined,message.modified(params).described())}else{this.update(undefined,message.released().described())}};current.reject=function(error){this.update(true,message.rejected({error:error}).described())};current.modified=function(params){this.update(true,message.modified(params).described())};this.deliveries.push(current);data=frame.payload}else{throw Error("frame sequence error: expected "+this.next_delivery_id+", got "+frame.performative.delivery_id)}current.incomplete=frame.performative.more;if(current.incomplete){current.data=data}else{receiver.credit--;receiver.delivery_count++;this.next_delivery_id++;receiver.dispatch("message",receiver._context({message:message.decode(data),delivery:current}))}}};Incoming.prototype.process=function(){if(this.updated.length>0){write_dispositions(this.updated);this.updated=[]}this.deliveries.pop_if(function(d){retu
 rn d.settled})};Incoming.prototype.on_begin=function(fields){this.remote_window=fields.outgoing_window};Incoming.prototype.on_flow=function(fields){this.remote_next_transfer_id=fields.next_outgoing_id;this.remote_window=fields.outgoing_window};Incoming.prototype.on_disposition=function(fields){var last=fields.last?fields.last:fields.first;for(var i=fields.first;i<=last;i++){var d=this.deliveries.by_id(i);if(d&&!d.remote_settled){var updated=false;if(fields.settled){d.remote_settled=fields.settled;d.link.dispatch("settled",d.link._context({delivery:d}))}}}};var Session=function(connection,local_channel){this.connection=connection;this.outgoing=new Outgoing;this.incoming=new Incoming;this.state=new EndpointState;this.local={channel:local_channel,handles:{}};this.local.begin=frames.begin({next_outgoing_id:this.outgoing.next_transfer_id,incoming_window:this.incoming.window,outgoing_window:this.outgoing.window});this.local.end=frames.end();this.remote={handles:{}};this.links={};this.opti
 ons={}};Session.prototype=Object.create(EventEmitter.prototype);Session.prototype.constructor=Session;Session.prototype.reset=function(){this.state.disconnected();this.outgoing=new Outgoing;this.incoming=new Incoming;this.remote={handles:{}};for(var l in this.links){this.links[l].reset()}};Session.prototype.dispatch=function(name,context){log.events("Session got event: "+name);if(this.listeners(name).length){EventEmitter.prototype.emit.apply(this,arguments);return true}else{return this.connection.dispatch.apply(this.connection,arguments)}};Session.prototype.output=function(frame,payload){this.connection._write_frame(this.local.channel,frame,payload)};Session.prototype.create_sender=function(name,opts){return this.create_link(name,link.Sender,opts)};Session.prototype.create_receiver=function(name,opts){return this.create_link(name,link.Receiver,opts)};function attach(factory,args,remote_terminus){var opts=args?args:{};if(typeof args==="string"){opts={};opts[remote_terminus]=args}if(!
 opts.name)opts.name=util.generate_uuid();var l=factory(opts.name,opts);for(var t in{source:0,target:0}){if(opts[t]){if(typeof opts[t]==="string"){opts[t]={address:opts[t]}}l["set_"+t](opts[t])}}l.attach();return l}Session.prototype.get_option=function(name,default_value){if(this.options[name]!==undefined)return this.options[name];else return this.connection.get_option(name,default_value)};Session.prototype.attach_sender=function(args){return attach(this.create_sender.bind(this),args,"target")};Session.prototype.open_sender=Session.prototype.attach_sender;Session.prototype.attach_receiver=function(args){return attach(this.create_receiver.bind(this),args,"source")};Session.prototype.open_receiver=Session.prototype.attach_receiver;Session.prototype.create_link=function(name,constructor,opts){var i=0;while(this.local.handles[i])i++;var l=new constructor(this,name,i,opts);this.links[name]=l;this.local.handles[i]=l;return l};Session.prototype.begin=function(){if(this.state.open()){this.co
 nnection._register()}};Session.prototype.open=Session.prototype.begin;Session.prototype.end=function(error){if(error)this.local.end.error=error;if(this.state.close()){this.connection._register()}};Session.prototype.close=Session.prototype.end;Session.prototype.is_open=function(){return this.connection.is_open()&&this.state.is_open()};Session.prototype.is_closed=function(){return this.connection.is_closed()||this.state.is_closed()};Session.prototype._process=function(){do{if(this.state.need_open()){this.output(this.local.begin.described())}this.outgoing.process();this.incoming.process();for(var k in this.links){this.links[k]._process()}if(this.state.need_close()){this.output(this.local.end.described())}}while(!this.state.has_settled())};Session.prototype.send=function(sender,tag,data,format){var d=this.outgoing.send(sender,tag,data,format);this.connection._register();return d};Session.prototype._write_flow=function(link){var fields={next_incoming_id:this.incoming.next_transfer_id,inc
 oming_window:this.incoming.window,next_outgoing_id:this.outgoing.next_transfer_id,outgoing_window:this.outgoing.window};if(link){if(link._get_drain())fields.drain=true;fields.delivery_count=link.delivery_count;fields.handle=link.local.handle;fields.link_credit=link.credit}this.output(frames.flow(fields).described())};Session.prototype.on_begin=function(frame){if(this.state.remote_opened()){if(!this.remote.channel){this.remote.channel=frame.channel}this.remote.begin=frame.performative;this.outgoing.on_begin(frame.performative);this.incoming.on_begin(frame.performative);this.open();this.dispatch("session_open",this._context())}else{throw Error("Begin already received")}};Session.prototype.on_end=function(frame){if(this.state.remote_closed()){this.remote.end=frame.performative;this.close();this.dispatch("session_close",this._context())}else{throw Error("End already received")}};Session.prototype.on_attach=function(frame){var name=frame.performative.name;var link=this.links[name];if(!li
 nk){link=frame.performative.role?this.create_sender(name):this.create_receiver(name)}this.remote.handles[frame.performative.handle]=link;
-link.on_attach(frame);link.remote.attach=frame.performative};Session.prototype.on_disposition=function(frame){if(frame.performative.role){log.events("Received disposition for outgoing transfers");this.outgoing.on_disposition(frame.performative)}else{log.events("Received disposition for incoming transfers");this.incoming.on_disposition(frame.performative)}this.connection._register()};Session.prototype.on_flow=function(frame){this.outgoing.on_flow(frame.performative);this.incoming.on_flow(frame.performative);if(frame.performative.handle!==undefined){this._get_link(frame).on_flow(frame)}this.connection._register()};Session.prototype._context=function(c){var context=c?c:{};context.session=this;return this.connection._context(context)};Session.prototype._get_link=function(frame){var handle=frame.performative.handle;var link=this.remote.handles[handle];if(!link){throw Error("Invalid handle "+handle)}return link};Session.prototype.on_detach=function(frame){this._get_link(frame).on_detach(f
 rame);var handle=frame.performative.handle;var link=this.remote.handles[handle];delete this.remote.handles[handle];delete this.local.handles[link.local.handle];delete this.links[link.name]};Session.prototype.on_transfer=function(frame){this.incoming.on_transfer(frame,this._get_link(frame))};module.exports=Session}).call(this,require("buffer").Buffer)},{"./endpoint.js":2,"./frames.js":3,"./link.js":4,"./log.js":5,"./message.js":6,"./types.js":12,"./util.js":13,buffer:19,events:23}],10:[function(require,module,exports){"use strict";var types=require("./types.js");var terminus={};var by_descriptor={};function define_terminus(def){var c=types.define_composite(def);terminus[def.name]=c.create;by_descriptor[Number(c.descriptor.numeric).toString(10)]=c;by_descriptor[c.descriptor.symbolic]=c}terminus.unwrap=function(field){if(field&&field.descriptor){var c=by_descriptor[field.descriptor.value];if(c){return new c(field.value)}else{console.log("Unknown terminus: "+field.descriptor)}}return nu
 ll};define_terminus({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:false},{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:true},{name:"capabilities",type:"symbol",multiple:true}]});define_terminus({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:false},{name:"dynamic_node_properties",type:"symbolic_map"},{name:"capabilities",type:"symbol",multiple:true}]});module.exports=terminus},{"./types.js":12}],11:[functi
 on(require,module,exports){(function(Buffer){"use strict";var frames=require("./frames.js");var log=require("./log.js");var Transport=function(identifier,protocol_id,frame_type,handler){this.identifier=identifier;this.protocol_id=protocol_id;this.frame_type=frame_type;this.handler=handler;this.pending=[];this.header_sent=undefined;this.header_received=undefined;this.write_complete=false;this.read_complete=false};Transport.prototype.has_writes_pending=function(){return this.pending.length>0};Transport.prototype.encode=function(frame){var buffer=frames.write_frame(frame);log.frames("["+this.identifier+"] PENDING: "+JSON.stringify(frame));this.pending.push(buffer)};Transport.prototype.write=function(socket){if(!this.header_sent){var buffer=new Buffer(8);var header={protocol_id:this.protocol_id,major:1,minor:0,revision:0};frames.write_header(buffer,header);socket.write(buffer);this.header_sent=header}for(var i=0;i<this.pending.length;i++){socket.write(this.pending[i]);log.raw("["+this.i
 dentifier+"] SENT: "+JSON.stringify(this.pending[i]))}this.pending=[]};Transport.prototype.read=function(buffer){var offset=0;if(!this.header_received){if(buffer.length<8){return offset}else{this.header_received=frames.read_header(buffer);log.frames("["+this.identifier+"] RECV: "+JSON.stringify(this.header_received));if(this.header_received.protocol_id!==this.protocol_id){throw Error("Invalid AMQP protocol id "+this.header_received.protocol_id+" expecting: "+this.protocol_id)}offset=8}}while(offset<buffer.length&&!this.read_complete){var frame_size=buffer.readUInt32BE(offset);log.io("["+this.identifier+"] got frame of size "+frame_size);if(buffer.length<offset+frame_size){log.io("["+this.identifier+"] incomplete frame; have only "+(buffer.length-offset)+" of "+frame_size);break}else{var frame=frames.read_frame(buffer.slice(offset,offset+frame_size));log.frames("["+this.identifier+"] RECV: "+JSON.stringify(frame));if(frame.type!==this.frame_type){throw Error("Invalid frame type: "+fr
 ame.type)}offset+=frame_size;if(frame.performative){frame.performative.dispatch(this.handler,frame)}}}return offset};module.exports=Transport}).call(this,require("buffer").Buffer)},{"./frames.js":3,"./log.js":5,buffer:19}],12:[function(require,module,exports){(function(Buffer){"use strict";function Typed(type,value){this.type=type;this.value=value}Typed.prototype.toString=function(){return this.value?this.value.toString():null};Typed.prototype.toLocaleString=function(){return this.value?this.value.toLocaleString():null};Typed.prototype.valueOf=function(){return this.value};Typed.prototype.toJSON=function(){return this.value&&this.value.toJSON?this.value.toJSON():this.value};function hex(i){return Number(i).toString(16)}var types={by_code:{}};Object.defineProperty(types,"MAX_UINT",{value:4294967295,writable:false,configurable:false});Object.defineProperty(types,"MAX_USHORT",{value:65535,writable:false,configurable:false});function define_type(name,typecode,annotations,empty_value){va
 r subcategory=typecode>>>4;var t;if(subcategory===4){t=function(){this.type=t;this.value=empty_value}}else if(subcategory===14||subcategory===15){t=function(v,code,descriptor){this.type=t;this.value=v;this.array_constructor={typecode:code};if(descriptor){this.array_constructor.descriptor=descriptor}}}else{t=function(v){this.type=t;this.value=v}}t.typecode=typecode;t.prototype=Object.create(Typed.prototype);t.toString=function(){return name+"#"+hex(typecode)};switch(subcategory){case 4:t.width=0;t.category="fixed";break;case 5:t.width=1;t.category="fixed";break;case 6:t.width=2;t.category="fixed";break;case 7:t.width=4;t.category="fixed";break;case 8:t.width=8;t.category="fixed";break;case 9:t.width=16;t.category="fixed";break;case 10:t.width=1;t.category="variable";break;case 11:t.width=4;t.category="variable";break;case 12:t.width=1;t.category="compound";break;case 13:t.width=4;t.category="compound";break;case 14:t.width=1;t.category="array";break;case 15:t.width=4;t.category="arra
 y";break}if(annotations){for(var a in annotations){t[a]=annotations[a]}}types.by_code[t.typecode]=t;types[name]=t;return t}function buffer_ops(name){return{read:function(buffer,offset){return buffer["read"+name](offset)},write:function(buffer,value,offset){buffer["write"+name](value,offset)}}}function buffer_ops_be(name){return{read:function(buffer,offset){return buffer["read"+name+"BE"](offset)},write:function(buffer,value,offset){buffer["write"+name+"BE"](value,offset)}}}var MAX_UINT=4294967296;var MIN_INT=-2147483647;function write_ulong(buffer,value,offset){if(typeof value==="number"||value instanceof Number){var hi=Math.floor(value/MAX_UINT);var lo=value%MAX_UINT;buffer.writeUInt32BE(hi,offset);buffer.writeUInt32BE(lo,offset+4)}else{value.copy(buffer,offset)}}function read_ulong(buffer,offset){var hi=buffer.readUInt32BE(offset);var lo=buffer.readUInt32BE(offset+4);if(hi<2097153){return hi*MAX_UINT+lo}else{return buffer.slice(offset,offset+8)}}function write_long(buffer,value,of
 fset){if(typeof value==="number"||value instanceof Number){var abs=Math.abs(value);var hi=Math.floor(abs/MAX_UINT);var lo=abs%MAX_UINT;buffer.writeInt32BE(hi,offset);buffer.writeUInt32BE(lo,offset+4);if(value<0){var carry=1;for(var i=0;i<8;i++){var index=offset+(7-i);var value=(buffer[index]^255)+carry;buffer[index]=value&255;carry=value>>8}}}else{value.copy(buffer,offset)}}function read_long(buffer,offset){var hi=buffer.readInt32BE(offset);var lo=buffer.readUInt32BE(offset+4);if(hi<2097153&&hi>-2097153){return hi*MAX_UINT+lo}else{return buffer.slice(offset,offset+8)}}define_type("Null",64,undefined,null);define_type("Boolean",86,buffer_ops("UInt8"));define_type("True",65,undefined,true);define_type("False",66,undefined,false);define_type("Ubyte",80,buffer_ops("UInt8"));define_type("Ushort",96,buffer_ops("UInt16BE"));define_type("Uint",112,buffer_ops("UInt32BE"));define_type("SmallUint",82,buffer_ops("UInt8"));define_type("Uint0",67,undefined,0);define_type("Ulong",128,{write:write_
 ulong,read:read_ulong});define_type("SmallUlong",83,buffer_ops("UInt8"));define_type("Ulong0",68,undefined,0);define_type("Byte",81,buffer_ops("Int8"));define_type("Short",97,buffer_ops("Int16BE"));define_type("Int",113,buffer_ops("Int32BE"));define_type("SmallInt",84,buffer_ops("Int8"));define_type("Long",129,{write:write_long,read:read_long});define_type("SmallLong",85,buffer_ops("Int8"));define_type("Float",114,buffer_ops_be("Float"));define_type("Double",130,buffer_ops_be("Double"));define_type("Decimal32",116);define_type("Decimal64",132);define_type("Decimal128",148);define_type("CharUTF32",115);define_type("Timestamp",131,{write:write_long,read:read_long});define_type("Uuid",152);define_type("Vbin8",160);define_type("Vbin32",176);define_type("Str8",161,{encoding:"utf8"});define_type("Str32",177,{encoding:"utf8"});define_type("Sym8",163,{encoding:"ascii"});define_type("Sym32",179,{encoding:"ascii"});define_type("List0",69,undefined,[]);define_type("List8",192);define_type("Lis
 t32",208);define_type("Map8",193);define_type("Map32",209);define_type("Array8",224);define_type("Array32",240);function is_one_of(o,typelist){for(var i=0;i<typelist.length;i++){if(o.type.typecode===typelist[i].typecode)return true}return false}types.is_ulong=function(o){return is_one_of(o,[types.Ulong,types.Ulong0,types.SmallUlong])};types.is_string=function(o){return is_one_of(o,[types.Str8,types.Str32])};types.is_symbol=function(o){return is_one_of(o,[types.Sym8,types.Sym32])};types.is_list=function(o){return is_one_of(o,[types.List0,types.List8,types.List32])};types.is_map=function(o){return is_one_of(o,[types.Map8,types.Map32])};types.wrap_boolean=function(v){return v?new types.True:new types.False};types.wrap_ulong=function(l){if(l===0)return new types.Ulong0;else return l>255?new types.Ulong(l):new types.SmallUlong(l)};types.wrap_uint=function(l){if(l===0)return new types.Uint0;else return l>255?new types.Uint(l):new types.SmallUint(l)};types.wrap_ushort=function(l){return ne
 w types.Ushort(l)};types.wrap_ubyte=function(l){return new types.Ubyte(l)};types.wrap_long=function(l){return l>127||l<-128?new types.Long(l):new types.SmallLong(l)};types.wrap_int=function(l){return l>127||l<-128?new types.Int(l):new types.SmallInt(l)};types.wrap_short=function(l){return new types.Short(l)};types.wrap_byte=function(l){return new types.Byte(l)};types.wrap_float=function(l){return new types.Float(l)};types.wrap_double=function(l){return new types.Double(l)};types.wrap_timestamp=function(l){return new types.Timestamp(l)};types.wrap_binary=function(s){return s.length>255?new types.Vbin32(s):new types.Vbin8(s)};types.wrap_string=function(s){return s.length>255?new types.Str32(s):new types.Str8(s)};types.wrap_symbol=function(s){return s.length>255?new types.Sym32(s):new types.Sym8(s)};types.wrap_list=function(l){if(l.length===0)return new types.List0;var items=l.map(types.wrap);return new types.List32(items)};types.wrap_map=function(m,key_wrapper){var items=[];for(var k 
 in m){items.push(key_wrapper?key_wrapper(k):types.wrap(k));items.push(types.wrap(m[k]))}return new types.Map32(items)};types.wrap_symbolic_map=function(m){return types.wrap_map(m,types.wrap_symbol)};types.wrap_array=function(l,code,descriptors){if(code){return new types.Array32(l,code,descriptors)}else{throw Error("An array must specify a type for its elements")}};types.wrap=function(o){var t=typeof o;if(t==="string"){return types.wrap_string(o)}else if(t=="boolean"){return o?new types.True:new types.False}else if(t=="number"||o instanceof Number){if(isNaN(o)){throw Error("Cannot wrap NaN! "+o)}else if(Math.floor(o)-o!==0){return new types.Double(o)}else if(o>0){if(o<MAX_UINT){return types.wrap_uint(o)}else{return types.wrap_ulong(o)}}else{if(o>MIN_INT){return types.wrap_int(o)}else{return types.wrap_long(o)}}}else if(o instanceof Date){return types.wrap_timestamp(o.getTime())}else if(o instanceof Typed){return o}else if(t=="undefined"||o===null){return new types.Null}else if(Array.
 isArray(o)){return types.wrap_list(o)}else{return types.wrap_map(o)}};types.wrap_described=function(value,descriptor){var result=types.wrap(value);if(descriptor){if(typeof descriptor==="string"){result=types.described(types.wrap_string(descriptor),result)}else if(typeof descriptor==="number"||descriptor instanceof Number){result=types.described(types.wrap_ulong(descriptor),result)}}return result};types.wrap_message_id=function(o){var t=typeof o;if(t==="string"){return types.wrap_string(o)}else if(t=="number"||o instanceof Number){return types.wrap_ulong(o)}else{throw Error("invalid message id:"+o)}};types.wrap_delivery_state=function(o){return new Null};function mapify(elements){var result={};for(var i=0;i+1<elements.length;){result[elements[i++]]=elements[i++]}return result}var by_descriptor={};types.unwrap=function(o,leave_described){if(o instanceof Typed){if(o.descriptor){var c=by_descriptor[o.descriptor.value];if(c){return new c(o.value)}else if(leave_described){return o}}var u=
 types.unwrap(o.value,true);return types.is_map(o)?mapify(u):u}else if(Array.isArray(o)){return o.map(function(i){return types.unwrap(i,true)})}else{return o}};types.described=function(descriptor,typedvalue){var o=Object.create(typedvalue);if(descriptor.length){o.descriptor=descriptor.shift();return described(descriptor,o)}else{o.descriptor=descriptor;return o}};function get_type(code){var type=types.by_code[code];if(!type){throw Error("Unrecognised typecode: "+hex(code))}return type}types.Reader=function(buffer){this.buffer=buffer;this.position=0};types.Reader.prototype.read_typecode=function(){return this.read_uint(1)};types.Reader.prototype.read_uint=function(width){var current=this.position;this.position+=width;var name=width>1?"readUInt"+width*8+"BE":"readUInt"+8;return this.buffer[name](current)};types.Reader.prototype.read_fixed_width=function(type){var current=this.position;this.position+=type.width;if(type.read){return type.read(this.buffer,current)}else{return this.buffer.s
 lice(current,this.position)}};types.Reader.prototype.read_variable_width=function(type){var size=this.read_uint(type.width);var slice=this.read_bytes(size);return type.encoding?slice.toString(type.encoding):slice};types.Reader.prototype.read=function(){var constructor=this.read_constructor();var value=this.read_value(get_type(constructor.typecode));return constructor.descriptor?types.described(constructor.descriptor,value):value};types.Reader.prototype.read_constructor=function(){var code=this.read_typecode();if(code===0){var d=[];d.push(this.read());var c=this.read_constructor();while(c.descriptor){d.push(c.descriptor);c=this.read_constructor()}return{typecode:c.typecode,descriptor:d.length==1?d[0]:d}}else{return{typecode:code}}};types.Reader.prototype.read_value=function(type){if(type.width===0){return new type}else if(type.category==="fixed"){return new type(this.read_fixed_width(type))}else if(type.category==="variable"){return new type(this.read_variable_width(type))}else if(ty
 pe.category==="compound"){return this.read_compound(type)}else if(type.category==="array"){return this.read_array(type)}else{throw Error("Invalid category for type: "+type)}};types.Reader.prototype.read_multiple=function(n,f){var read_fn=f?f:this.read.bind(this);var items=[];while(items.length<n){items.push(read_fn.apply(this))}return items};types.Reader.prototype.read_size_count=function(width){return{size:this.read_uint(width),count:this.read_uint(width)}};types.Reader.prototype.read_compound=function(type){var limits=this.read_size_count(type.width);return new type(this.read_multiple(limits.count))};types.Reader.prototype.read_array=function(type){var limits=this.read_size_count(type.width);var constructor=this.read_constructor();var value=new type(this.read_multiple(limits.count,this.read_value.bind(this,get_type(constructor.typecode))),constructor.typecode,constructor.descriptor);return value};types.Reader.prototype.toString=function(){var s="buffer@"+this.position;if(this.posi
 tion)s+=": ";for(var i=this.position;i<this.buffer.length;i++){if(i>0)s+=",";s+="0x"+Number(this.buffer[i]).toString(16)}return s};types.Reader.prototype.reset=function(){this.position=0};types.Reader.prototype.skip=function(bytes){this.position+=bytes};types.Reader.prototype.read_bytes=function(bytes){var current=this.position;this.position+=bytes;return this.buffer.slice(current,this.position)};types.Reader.prototype.remaining=function(){return this.buffer.length-this.position};types.Writer=function(buffer){this.buffer=buffer?buffer:new Buffer(1024);this.position=0};types.Writer.prototype.toBuffer=function(){return this.buffer.slice(0,this.position)};function max(a,b){return a>b?a:b}types.Writer.prototype.ensure=function(length){if(this.buffer.length<length){var bigger=new Buffer(max(this.buffer.length*2,length));this.buffer.copy(bigger);this.buffer=bigger}};types.Writer.prototype.write_typecode=function(code){this.write_uint(code,1)};types.Writer.prototype.write_uint=function(val
 ue,width){var current=this.position;this.ensure(this.position+width);this.position+=width;var name=width>1?"writeUInt"+width*8+"BE":"writeUInt"+8;if(!this.buffer[name]){throw Error("Buffer doesn't define "+name)}return this.buffer[name](value,current)};types.Writer.prototype.write_fixed_width=function(type,value){var current=this.position;this.ensure(this.position+type.width);this.position+=type.width;if(type.write){type.write(this.buffer,value,current)}else if(value.copy){value.copy(this.buffer,current)}else{throw Error("Can't handle write for "+type)}};types.Writer.prototype.write_variable_width=function(type,value){var source=type.encoding?new Buffer(value,type.encoding):new Buffer(value);this.write_uint(source.length,type.width);this.write_bytes(source)};types.Writer.prototype.write_bytes=function(source){var current=this.position;this.ensure(this.position+source.length);this.position+=source.length;source.copy(this.buffer,current)};types.Writer.prototype.write_constructor=funct
 ion(typecode,descriptor){if(descriptor){this.write_typecode(0);this.write(descriptor)}this.write_typecode(typecode)};types.Writer.prototype.write=function(o){if(!(o instanceof Typed)){throw Error("Can't write "+JSON.stringify(o))}this.write_constructor(o.type.typecode,o.descriptor);this.write_value(o.type,o.value,o.array_constructor)};types.Writer.prototype.write_value=function(type,value,constructor){if(type.width===0){return}else if(type.category==="fixed"){this.write_fixed_width(type,value)}else if(type.category==="variable"){this.write_variable_width(type,value)}else if(type.category==="compound"){this.write_compound(type,value)}else if(type.category==="array"){this.write_array(type,value,constructor)}else{throw Error("Invalid category "+type.category+" for type: "+type)}};types.Writer.prototype.backfill_size=function(width,saved){var gap=this.position-saved;this.position=saved;this.write_uint(gap-width,width);this.position+=gap-width};types.Writer.prototype.write_compound=funct
 ion(type,value){var saved=this.position;this.position+=type.width;this.write_uint(value.length,type.width);for(var i=0;i<value.length;i++){if(value[i]===undefined||value[i]===null){this.write(new types.Null)}else{this.write(value[i])}}this.backfill_size(type.width,saved)};types.Writer.prototype.write_array=function(type,value,constructor){var saved=this.position;this.position+=type.width;this.write_uint(value.length,type.width);this.write_constructor(constructor.typecode,constructor.descriptor);var type=get_type(constructor.typecode);for(var i=0;i<value.length;i++){this.write_value(type,value[i])}this.backfill_size(type.width,saved)};types.Writer.prototype.toString=function(){var s="buffer@"+this.position;if(this.position)s+=": ";for(var i=0;i<this.position;i++){if(i>0)s+=",";s+=("00"+Number(this.buffer[i]).toString(16)).slice(-2)}return s};types.Writer.prototype.skip=function(bytes){this.ensure(this.position+bytes);this.position+=bytes};types.Writer.prototype.clear=function(){this.
 buffer.fill(0);this.position=0};types.Writer.prototype.remaining=function(){return this.buffer.length-this.position};function get_constructor(typename){if(typename==="symbol"){return{typecode:types.Sym8.typecode}}throw Error("TODO: Array of type "+typename+" not yet supported")}function wrap_field(definition,instance){if(instance!==undefined&&instance!==null){if(Array.isArray(instance)){if(!definition.multiple){throw Error("Field "+definition.name+" does not support multiple values, got "+JSON.stringify(instance))}var constructor=get_constructor(definition.type);return types.wrap_array(instance,constructor.typecode,constructor.descriptor)}else if(definition.type==="*"){return instance}else{var wrapper=types["wrap_"+definition.type];if(wrapper){return wrapper(instance)}else{throw Error("No wrapper for field "+definition.name+" of type "+definition.type)}}}else if(definition.mandatory){throw Error("Field "+definition.name+" is mandatory")}else{return new types.Null}}function get_acces
 sors(index,field_definition){var getter=function(){return field_definition.type==="*"?this.value[index]:types.unwrap(this.value[index])};var setter=function(o){this.value[index]=wrap_field(field_definition,o)};return{get:getter,set:setter,enumerable:true,configurable:false}}types.define_composite=function(def){var c=function(fields){this.value=fields?fields:[]};c.descriptor={numeric:def.code,symbolic:"amqp:"+def.name+":list"};c.prototype.dispatch=function(target,frame){target["on_"+def.name](frame)};for(var i=0;i<def.fields.length;i++){var f=def.fields[i];Object.defineProperty(c.prototype,f.name,get_accessors(i,f))}c.toString=function(){return def.name+"#"+Number(def.code).toString(16)};c.prototype.toJSON=function(){var o={};for(var f in this){if(f!=="value"&&this[f]){o[f]=this[f]}}return o};c.create=function(fields){var o=new c;for(var f in fields){o[f]=fields[f]}return o};c.prototype.described=function(fields){return types.described(types.wrap_ulong(c.descriptor.numeric),types.wra
 p_list(this.value))};return c};function add_type(def){var c=types.define_composite(def);types["wrap_"+def.name]=function(fields){return c.create(fields).described()};by_descriptor[Number(c.descriptor.numeric).toString(10)]=c;by_descriptor[c.descriptor.symbolic]=c}add_type({name:"error",code:29,fields:[{name:"condition",type:"symbol",mandatory:true},{name:"description",type:"string"},{name:"info",type:"map"}]});module.exports=types}).call(this,require("buffer").Buffer)},{buffer:19}],13:[function(require,module,exports){"use strict";var util={};util.generate_uuid=function(){var uuid="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=="x"?r:r&3|8;return v.toString(16)});return uuid};util.clone=function(o){var copy=Object.create(o.prototype||{});var names=Object.getOwnPropertyNames(o);for(var i=0;i<names.length;i++){var key=names[i];copy[key]=o[key]}return copy};module.exports=util},{}],14:[function(require,module,exports){(function(Buffer){"
 use strict";function nulltransform(data){return data}function from_arraybuffer(data){if(data instanceof ArrayBuffer)return new Buffer(new Uint8Array(data));else return new Buffer(data)}function to_typedarray(data){return new Uint8Array(data)}function wrap(ws){var data_recv=nulltransform;var data_send=nulltransform;if(ws.binaryType){ws.binaryType="arraybuffer";data_recv=from_arraybuffer;data_send=to_typedarray}return{end:function(){ws.close()},write:function(data){try{ws.send(data_send(data),{binary:true})}catch(e){ws.onerror(e)}},on:function(event,handler){if(event==="data"){ws.onmessage=function(msg_evt){handler(data_recv(msg_evt.data))}}else if(event==="end"){ws.onclose=handler}else if(event==="error"){ws.onerror=handler}else{console.log("ERROR: Attempt to set unrecognised handler on websocket wrapper: "+event)}},get_id_string:function(){return ws.url}}}module.exports={connect:function(Impl){return function(url,protocols,options){return function(){return{connect:function(port_igno
 re,host_ignore,options_ignore,callback){v

<TRUNCATED>

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