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 2017/09/22 17:23:14 UTC

[04/19] qpid-dispatch git commit: DISPATCH-834 Initial commit of config file editor

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/93b9fa51/console/config/html/qdrTopology.html
----------------------------------------------------------------------
diff --git a/console/config/html/qdrTopology.html b/console/config/html/qdrTopology.html
new file mode 100644
index 0000000..0f605bd
--- /dev/null
+++ b/console/config/html/qdrTopology.html
@@ -0,0 +1,219 @@
+<!--
+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 id="buttonBar" class="navbar-primary">
+            Current topology
+            <select ng-model="mockTopologyDir" ng-options="item for item in mockTopologies"></select>
+            <button class="btn btn-primary" type="button" ng-click="Publish()">Publish</button>
+            <button class="btn btn-primary" type="button" ng-click="Clear()">Clear</button>
+            <button class="btn btn-primary pull-right" type="button" ng-click="doSettings()">Settings</button>
+            <button class="btn btn-primary pull-right" type="button" ng-click="showNewDlg()">New Topology</button>
+            <div class="selected-node pull-right">
+                <button class="btn btn-primary" type="button" ng-click="addAnotherNode(true)"><b class="plus caret"></b> Add new router</button>
+                <button id="action_button" class="btn btn-primary" type="button" ng-disabled="!selected_node" ng-click="showActions($event)">Actions <b class="down caret"></b></button> on selected router
+            </div>
+        </div>
+        <div id="topology"><!-- d3 toplogy here --></div>
+        <div id="svg_context_menu" class="contextMenu">
+            <ul>
+                <li ng-click="addAnotherNode()">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>
+                <li class="context-separator"></li>
+                <li ng-click="linkConfig()">Set port for connection.</li>
+            </ul>
+        </div>
+        <div id="svg_legend"></div>
+    <div id="action_menu" class="contextMenu">
+        <ul>
+            <li ng-click="editSection(selected_node, 'router')">Edit router info</li>
+            <li ng-click="setRouterHost(selected_node)">Set router host</li>
+
+            <li class="context-separator"></li>
+            <li ng-click="editSection(selected_node, 'log', 'new')">Add new log section</li>
+            <li ng-repeat="log in getSectionList(selected_node, 'log')" ng-click="editSection(selected_node, 'log', log)">Edit/Delete {{log}} log section</li>
+
+            <li class="context-separator"></li>
+            <li ng-click="editSection(selected_node, 'sslProfile', 'new')">Add new sslProfile section</li>
+            <li ng-repeat="ssl in getSectionList(selected_node, 'sslProfile')" ng-click="editSection(selected_node, 'sslProfile', ssl)">Edit/Delete {{ssl}} sslProfile section</li>
+
+            <li class="context-separator"></li>
+            <li ng-click="editSection(selected_node, 'connector', 'new')">Add new connector section</li>
+            <li ng-click="editSection(selected_node, 'connector', 'artemis')">Add new connector for Artemis broker</li>
+            <li ng-click="editSection(selected_node, 'connector', 'qpid')">Add new connector for Qpid broker</li>
+            <!-- <li ng-repeat="connector in getSectionList(selected_node, 'connector')" ng-click="editSection(selected_node, 'connector', connector)">Edit/Delete {{connector}} connector section</li> -->
+
+            <li class="context-separator"></li>
+            <li ng-click="editSection(selected_node, 'listener', 'new')">Add new listener section</li>
+            <li ng-if="!hasConsoleListener()" ng-click="addConsoleListener(selected_node)">Add listener for console</li>
+            <li ng-if="hasConsoleListener(selected_node)" ng-click="delConsoleListener(selected_node)">Delete listener for console</li>
+            <!-- <li ng-repeat="listener in getSectionList(selected_node, 'listener')" ng-click="editSection(selected_node, 'listener', listener)">Edit/Delete listener on port {{listener}} </li> -->
+
+            <li class="context-separator"></li>
+            <li ng-click="delNode(selected_node)">Delete this node</li>
+            <li ng-click="showConfig(selected_node)">Show generated config</li>
+        </ul>
+    </div>
+    <div id="client_context_menu" class="contextMenu">
+        <ul>
+            <li ng-click="delNode(selected_node)">Delete</li>
+            <li class="context-separator"></li>
+            <li ng-click="editThisSection(selected_node)">Edit</li>
+        </ul>
+    </div>
+</div>
+
+
+<script type="text/ng-template" id="settings-template.html">
+    <form novalidate name="editForm" ng-submit="setSettings()">
+    <div class="modal-header">
+        <h3 class="modal-title">Global settings</h3>
+    </div>
+    <div class="modal-body">
+
+            <fieldset>
+                <div ng-repeat="attribute in entity.attributes">
+                    <label class="form-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 name="{{attribute.name}}" type="radio" ng-model="attribute.value" ng-value="true"> True</label>
+                        <label><input name="{{attribute.name}}" type="radio" ng-model="attribute.value" ng-value="false"> False</label>
+                    </div>
+                    <div ng-if="attribute.input == 'file'">
+                        <input type="file" id="FileUpload" custom-on-change="uploadFile" webkitdirectory mozdirectory msdirectory odirectory directory multiple />
+                    </div>
+                </div>
+            </fieldset>
+
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" type="submit" ng-click="setSettings()" ng-disabled="editForm.$invalid" >OK</button>
+        <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
+    </div>
+    </form>
+</script>
+
+
+<!--
+    This is the template for the node add/edit dialog
+-->
+<script type="text/ng-template" id="node-config-template.html">
+    <form novalidate name="editForm" ng-submit="save()">
+    <div class="modal-header">
+        <h3 class="modal-title">{{title}}</h3>
+    </div>
+    <div class="modal-body">
+
+            <fieldset>
+                <div ng-repeat="attribute in entities[0].attributes">
+                    <div class="form-input-container" tooltip-append-to-body="true" tooltip-placement="right" uib-tooltip-html="attributeDescription" ng-mouseenter="setDescription(attribute, $event)" tooltip-class="edit-tooltip">
+                        <label class="form-label" for="{{attribute.name}}">{{attribute.humanName}}</label>
+                        <div ng-if="attribute.input == 'input'">
+                            <input class="edit_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 class="edit_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 class="edit_input" 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>
+                </div>
+            </fieldset>
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" type="submit" ng-disabled="editForm.$invalid" ng-click="save()">OK</button>
+        <button ng-if="showDelete()" class="btn btn-secondary" type="button" ng-click="del()">Delete</button>
+        <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
+    </div>
+    </form>
+</script>
+
+<script type="text/ng-template" id="show-config-template.html">
+    <form novalidate ng-submit="ok()">
+    <div class="modal-header">
+        <h3 class="modal-title">Current config file if published</h3>
+    </div>
+    <div class="modal-body">
+            <fieldset>
+                <pre class="config-area">{{config}}</pre>
+            </fieldset>
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" type="submit" ng-click="ok()">OK</button>
+    </div>
+    </form>
+</script>
+
+<script type="text/ng-template" id="new-config-template.html">
+
+    <form novalidate name="editForm" ng-submit="setSettings()">
+    <div class="modal-header">
+        <h3 class="modal-title">Create new topology</h3>
+    </div>
+    <div class="modal-body">
+
+            <label for="newTopo" class="entity-description">Enter a new topology directory name</label>
+            <fieldset>
+                <input type="text" name="newTopo" id="newTopo" ng-model="newTopology" ng-required="true" class="ui-widget-content ui-corner-all"/>
+            </fieldset>
+
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" type="submit" ng-click="setSettings()">OK</button>
+        <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
+    </div>
+    </form>
+</script>
+
+<script type="text/ng-template" id="set-router-host.html">
+    <form novalidate name="editForm" ng-submit="setSettings()">
+    <div class="modal-header">
+        <h3 class="modal-title">Set router host</h3>
+    </div>
+    <div class="modal-body">
+        <div class="description">
+            Set the host of all listeners on this router. Also ensure that all internal connectors to this router use this host.
+        </div>
+            <label for="host" class="entity-description">Enter a new topology directory name</label>
+            <fieldset>
+                <input type="text" name="host" id="host" ng-model="host" ng-required="true" class="ui-widget-content ui-corner-all"/>
+            </fieldset>
+
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" type="submit" ng-click="setSettings()">OK</button>
+        <button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
+    </div>
+    </form>
+</script>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/93b9fa51/console/config/index.html
----------------------------------------------------------------------
diff --git a/console/config/index.html b/console/config/index.html
new file mode 100644
index 0000000..64eb53a
--- /dev/null
+++ b/console/config/index.html
@@ -0,0 +1,92 @@
+<!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
+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.
+-->
+<html xmlns:ng="http://angularjs.org">
+
+<head>
+
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <title>Qpid Dispatch Config Processor</title>
+
+    <link rel="shortcut icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
+
+    <link href='bower_components/font-awsome/css/font-awesome.min.css' type="text/css">
+    <link href="bower_components/jquery-ui/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type='text/css'>
+    <link href="bower_components/patternfly/dist/css/patternfly.min.css" rel="stylesheet" type='text/css'>
+    <link href="bower_components/patternfly/dist/css/patternfly-additions.min.css" rel="stylesheet" type='text/css'>
+
+    <link rel="stylesheet" href="css/plugin.css" type="text/css"/>
+    <link rel="stylesheet" href="css/dispatch.css" type="text/css"/>
+    <link rel="stylesheet" href="css/site-base.css" type="text/css"/>
+    <link rel="stylesheet" href="css/mock.css" type="text/css"/>
+
+</head>
+
+<body ng-app="QDR">
+
+    <nav class="navbar navbar-default navbar-pf" role="navigation">
+        <div class="navbar-header">
+            <a class="navbar-brand" href="/">
+                <span class="logo">Qpid Dispatch config file processor</span>
+            </a>
+        </div>
+    </nav>
+
+    <div class="container-fluid">
+        <div class="row">
+            <div id="main-container">
+                <div ng-view></div>
+            </div>
+        </div>
+    </div>
+
+
+<!--[if lt IE 9]>
+<script src="bower_components/html5shiv/dist/html5shiv.min.js"></script>
+<![endif]-->
+
+<script src="bower_components/jquery/dist/jquery.min.js"></script>
+<script src="bower_components/jquery-ui/jquery-ui.min.js"></script>
+
+<!-- Angular -->
+<script src="bower_components/angular/angular.min.js"></script>
+<script src="bower_components/angular-route/angular-route.min.js"></script>
+<script src="bower_components/angular-resource/angular-resource.min.js"></script>
+<script src="bower_components/angular-animate/angular-animate.min.js"></script>
+<script src="bower_components/angular-sanitize/angular-sanitize.min.js"></script>
+<script src="bower_components/angular-bootstrap/ui-bootstrap.min.js"></script>
+<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>
+
+<script src='bower_components/d3/d3.min.js'></script>
+<script src="bower_components/notifyjs/dist/notify.js"></script>
+
+<script type="text/javascript" src="js/dispatchPlugin.js"></script>
+<script type="text/javascript" src="js/qdrService.js"></script>
+<script type="text/javascript" src="js/qdrTopology.js"></script>
+<script type="text/javascript" src="js/qdrNewNode.js"></script>
+
+
+<script type="text/javascript">
+        //angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
+</script>
+
+</body>
+</html>
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/93b9fa51/console/config/js/dispatchPlugin.js
----------------------------------------------------------------------
diff --git a/console/config/js/dispatchPlugin.js b/console/config/js/dispatchPlugin.js
new file mode 100644
index 0000000..fa18322
--- /dev/null
+++ b/console/config/js/dispatchPlugin.js
@@ -0,0 +1,165 @@
+/*
+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 = "";
+
+  /**
+   * @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 = "";
+  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', 'ngSanitize', 'ui.bootstrap']);
+
+  Core = {
+    notification: function (severity, msg) {
+      $.notify(msg, severity)
+    }
+  }
+
+  // set up the routing for this plugin
+  QDR.module.config(function($routeProvider) {
+    $routeProvider
+      .otherwise({
+          templateUrl: QDR.templatePath + 'qdrTopology.html'
+        })
+  });
+
+  QDR.module.config(function ($compileProvider) {
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension|file|blob):/);
+    $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/);
+  })
+
+  QDR.module.filter('to_trusted', ['$sce', function($sce){
+          return function(text) {
+              return $sce.trustAsHtml(text);
+          };
+    }]);
+
+  QDR.module.filter('humanify', 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", function ($rootScope, $route, $timeout, $location, $log, QDRService) {
+    QDR.log = new QDR.logger($log);
+    QDR.log.info("*************creating config editor************");
+    QDRService.getSchema(function () {
+      QDR.log.debug("got schema")
+    })
+  }]);
+
+  QDR.module.config(['$qProvider', function ($qProvider) {
+      $qProvider.errorOnUnhandledRejections(false);
+  }]);
+
+  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/93b9fa51/console/config/js/qdrNewNode.js
----------------------------------------------------------------------
diff --git a/console/config/js/qdrNewNode.js b/console/config/js/qdrNewNode.js
new file mode 100644
index 0000000..39cafdf
--- /dev/null
+++ b/console/config/js/qdrNewNode.js
@@ -0,0 +1,299 @@
+/*
+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.NodeDialogController", function($scope, $sce, $uibModalInstance, QDRService, node, entityType, context, entityKey, maxPort, hasLinks) {
+    var newname = node.name
+    $scope.context = context
+    if (!angular.isDefined(context))
+      $scope.context = 'new'
+    var newContext = {"new":maxPort+1, "artemis":61616, "qpid":5672, 'log':'', 'sslProfile':''}
+    $scope.title = ((context && context in newContext) ? "Edit " : "Add ") + entityType + " section"
+    if (context === 'artemis')
+      $scope.title += " to an Artemis broker"
+    if (context === 'qpid')
+      $scope.title += " to a Qpid broker"
+    var schema = QDRService.schema;
+    var myEntities = []
+    myEntities.push(entityType)
+    var readOnlyAttrs = { 'router': ['version', 'id', 'identity'],
+                          'log': ['name', 'identity'],
+                          'linkRoute': ['identity'],
+                          "sslProfile": ['identity'],
+                          "connector": ['identity']}
+
+    var typeMap = {
+      integer: 'number',
+      string: 'text',
+      path: 'text',
+      boolean: 'boolean'
+    };
+    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 = [];
+
+    var getEdivAttr = function (ediv, key) {
+      for (var i=0; i<ediv.attributes.length; i++) {
+        if (ediv.attributes[i].name === key)
+          return ediv.attributes[i]
+      }
+    }
+
+    // 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 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 read-only fields
+            if (readOnlyAttrs[tabName] && readOnlyAttrs[tabName].indexOf(key) >= 0)
+              return null;
+            // skip deprecated and statistics fields
+            if (key == value.description.startsWith('Deprecated') || value.graph)
+              return null;
+            var val = value['default'];
+            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 || key === entityKey || key === 'host')? true : false,
+              unique: value.unique
+            };
+          })
+        }
+      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 (node[entityName+'s'] && context in node[entityName+'s']) {
+        // fill form with existing data
+        var o = node[entityName+'s'][context]
+        ediv.attributes.forEach( function (attr) {
+          if (attr['name'] in o) {
+            attr['value'] = o[attr['name']]
+            if (attr['type'] === 'number') {
+              try {
+                attr['value'] = parseInt(attr['value'])
+              }
+              catch (e) {
+                attr['value'] = 0
+              }
+            }
+
+            // if the form has a select dropdown, set the selected to the current value
+            if (Array.isArray(attr['rawtype']))
+              attr.selected = attr['value']
+          }
+        })
+      }
+
+      if (ediv.actualName == 'router') {
+        ediv.attributes.forEach( function (attr) {
+          if (attr['name'] in node) {
+            attr['value'] = node[attr['name']]
+          }
+        })
+        // if we have any new links (connectors), then the router's mode should be interior
+        var roleAttr = getEdivAttr(ediv, 'mode')
+        if (hasLinks && roleAttr) {
+          roleAttr.value = roleAttr.selected = "interior";
+        } else {
+          roleAttr.value = roleAttr.selected = "standalone";
+        }
+      }
+      if (ediv.actualName == 'listener' && context === 'new') {
+        // find max port number that is used in all the listeners
+        var port = getEdivAttr(ediv, 'port')
+        if (port)
+          port.value = ++maxPort
+        var host = getEdivAttr(ediv, 'host')
+        if (host && node.host)
+          host.value = node.host
+      }
+      if (ediv.actualName == 'connector' && context in newContext) {
+        // find max port number that is used in all the listeners
+        var port = getEdivAttr(ediv, 'port')
+        if (port) {
+          port.value = newContext[context]
+        }
+        if (context === 'artemis' || context === 'qpid') {
+          var role = getEdivAttr(ediv, 'role')
+          if (role) {
+            role.value = role.selected = 'route-container'
+          }
+        }
+        context = 'new'
+      }
+      // special case for required log.module since it doesn't have a default
+      if (ediv.actualName == 'log') {
+        // initialize module to 'DEFAULT'
+        var moduleAttr = getEdivAttr(ediv, 'module')
+        if (moduleAttr)
+          moduleAttr.value = moduleAttr.selected = "DEFAULT"
+        // adding a new log section and we already have a section. select 1st unused module
+        if (context === 'new' && node.logs) {
+          var modules = ent.attributes.module.type
+          var availableModules = modules.filter( function (module) {
+             return !(module in node.logs)
+          })
+          if (availableModules.length > 0) {
+            moduleAttr.value = moduleAttr.selected = availableModules[0]
+          }
+        } else if (node.logs && context in node.logs) {
+          // fill form with existing data
+          var log = node.logs[context]
+          ediv.attributes.forEach( function (attr) {
+            if (attr['name'] in log) {
+              attr['value'] = log[attr['name']]
+              if (attr['name'] == 'module')
+                attr.selected = attr['value']
+            }
+          })
+        }
+      }
+      // sort ediv.attributes on name
+      var allNames = ediv.attributes.map( function (attr) {
+        return attr['name']
+      })
+      allNames.sort()
+
+      // move all required entities to the top
+      for (var i=0; i<ediv.attributes.length; i++) {
+        var attr = ediv.attributes[i]
+        if (attr.required) {
+          allNames.move(allNames.indexOf(attr.name), 0)
+        }
+      }
+
+      // move entities key into first position
+      var keyIndex = allNames.findIndex(function (name) {
+        return name === entityKey
+      })
+      if (keyIndex > 0) {
+        allNames.move(keyIndex, 0)
+      }
+      // now order the entity attributes by allNames
+      ediv.attributes.sort(function(attr1, attr2){
+          return allNames.indexOf(attr1['name']) - allNames.indexOf(attr2['name'])
+      });
+      $scope.entities.push(ediv);
+    })
+
+    $scope.testPattern = function(attr) {
+      if (attr.rawtype == 'path')
+        return /^(\/)?([^/\0]+(\/)?)+$/;
+      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.setDescription = function(attr, e) {
+      $scope.attributeDescription = $sce.trustAsHtml(attr.description +
+          '<div class="attr-type">Type: ' + JSON.stringify(attr.rawtype, null, 1) + '</div>' +
+          '<div class="attr-required">' + (attr.required ? "required" : '') + '</div>' +
+          '<div class="attr-unique">' + (attr.unique ? "Must be unique" : '') + '</div>')
+    }
+      // handle the save button click
+      // copy the dialog's values to the original node
+    $scope.save = function() {
+      $uibModalInstance.close({
+        entities: $scope.entities,
+      });
+    }
+    $scope.cancel = function() {
+      $uibModalInstance.close()
+    };
+    $scope.del = function() {
+      $uibModalInstance.close({del: true})
+    }
+    $scope.showDelete = function () {
+      return !(context === 'new' || !context)
+    }
+
+  });
+
+  return QDR;
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/93b9fa51/console/config/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/config/js/qdrService.js b/console/config/js/qdrService.js
new file mode 100644
index 0000000..149f94e
--- /dev/null
+++ b/console/config/js/qdrService.js
@@ -0,0 +1,327 @@
+/*
+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 = {
+
+      connectActions: [],
+      schema: undefined,
+
+      addConnectAction: function(action) {
+        if (angular.isFunction(action)) {
+          self.connectActions.push(action);
+        }
+      },
+      executeConnectActions: function() {
+        self.connectActions.forEach(function(action) {
+          try {
+            action.apply();
+          } catch (e) {
+            // in case the page that registered the handler has been unloaded
+            QDR.log.info(e.message)
+          }
+        });
+        self.connectActions = [];
+      },
+      nameFromId: function(id) {
+        return id.split('/')[3];
+      },
+
+      humanify: function(s) {
+        if (!s || s.length === 0)
+          return s;
+        var t = s.charAt(0).toUpperCase() + s.substr(1).replace(/[A-Z]/g, ' $&');
+        return t.replace(".", " ");
+      },
+      pretty: function(v) {
+        var formatComma = d3.format(",");
+        if (!isNaN(parseFloat(v)) && isFinite(v))
+          return formatComma(v);
+        return v;
+      },
+
+      // 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 === 'route-container' && d.properties.product === 'apache-activemq-artemis';
+      },
+
+      isQpid: function(d) {
+        return d.nodeType === 'route-container' && (d.properties && d.properties.product === 'qpid-cpp');
+      },
+
+      isAConsole: function(properties, connectionId, nodeType, key) {
+        return self.isConsole({
+          properties: properties,
+          connectionId: connectionId,
+          nodeType: nodeType,
+          key: key
+        })
+      },
+      isConsole: function(d) {
+        // use connection properties if available
+        return (d && d['properties'] && d['properties']['console_identifier'] === 'Dispatch console')
+      },
+
+      flatten: function(attributes, result) {
+        var flat = {}
+        attributes.forEach(function(attr, i) {
+          if (result && result.length > i)
+            flat[attr] = result[i]
+        })
+        return flat;
+      },
+      getSchema: function(callback) {
+        self.sendMethod("GET-SCHEMA", {}, function (response) {
+          for (var entityName in response.entityTypes) {
+            var entity = response.entityTypes[entityName]
+            if (entity.deprecated) {
+              delete response.entityTypes[entityName]
+            } else {
+              for (var attributeName in entity.attributes) {
+                var attribute = entity.attributes[attributeName]
+                if (attribute.deprecated) {
+                  delete response.entityTypes[entityName].attributes[attributeName]
+                }
+              }
+            }
+          }
+          self.schema = response
+          callback()
+        })
+      },
+
+      sendMethod: function(operation, props, callback) {
+        setTimeout(function () {
+          props['operation'] = operation
+          $.post( "http://localhost:8000", JSON.stringify(props), function (response, status) {
+            callback(response)
+          }, "json" )
+        }, 1)
+      }
+
+    }
+    return self;
+  }]);
+
+  return QDR;
+
+}(QDR || {}));
+
+(function() {
+  console.dump = function(o) {
+    if (window.JSON && window.JSON.stringify)
+      QDR.log.info(JSON.stringify(o, undefined, 2));
+    else
+      console.log(o);
+  };
+})();
+
+function ngGridFlexibleHeightPlugin (opts) {
+    var self = this;
+    self.grid = null;
+    self.scope = null;
+    self.init = function (scope, grid, services) {
+        self.domUtilityService = services.DomUtilityService;
+        self.grid = grid;
+        self.scope = scope;
+        var recalcHeightForData = function () { setTimeout(innerRecalcForData, 1); };
+        var innerRecalcForData = function () {
+            var gridId = self.grid.gridId;
+            var footerPanelSel = '.' + gridId + ' .ngFooterPanel';
+            if (!self.grid.$topPanel || !self.grid.$canvas)
+              return;
+            var extraHeight = self.grid.$topPanel.height() + $(footerPanelSel).height();
+            var naturalHeight = self.grid.$canvas.height() + 1;
+            if (opts != null) {
+                if (opts.minHeight != null && (naturalHeight + extraHeight) < opts.minHeight) {
+                    naturalHeight = opts.minHeight - extraHeight - 2;
+                }
+                if (opts.maxHeight != null && (naturalHeight + extraHeight) > opts.maxHeight) {
+                    naturalHeight = opts.maxHeight;
+                }
+            }
+
+            var newViewportHeight = naturalHeight + 3;
+            if (!self.scope.baseViewportHeight || self.scope.baseViewportHeight !== newViewportHeight) {
+                self.grid.$viewport.css('height', newViewportHeight + 'px');
+                self.grid.$root.css('height', (newViewportHeight + extraHeight) + 'px');
+                self.scope.baseViewportHeight = newViewportHeight;
+                self.domUtilityService.RebuildGrid(self.scope, self.grid);
+            }
+        };
+        self.scope.catHashKeys = function () {
+            var hash = '',
+                idx;
+            for (idx in self.scope.renderedRows) {
+                hash += self.scope.renderedRows[idx].$$hashKey;
+            }
+            return hash;
+        };
+        self.scope.$watch('catHashKeys()', innerRecalcForData);
+        self.scope.$watch(self.grid.config.data, recalcHeightForData);
+    };
+}
+
+if (!String.prototype.startsWith) {
+  String.prototype.startsWith = function (searchString, position) {
+    return this.substr(position || 0, searchString.length) === searchString
+  }
+}
+
+if (!String.prototype.endsWith) {
+  String.prototype.endsWith = function(searchString, position) {
+      var subjectString = this.toString();
+      if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
+        position = subjectString.length;
+      }
+      position -= searchString.length;
+      var lastIndex = subjectString.lastIndexOf(searchString, position);
+      return lastIndex !== -1 && lastIndex === position;
+  };
+}
+
+if (typeof Object.assign != 'function') {
+  // Must be writable: true, enumerable: false, configurable: true
+  Object.defineProperty(Object, "assign", {
+    value: function assign(target, varArgs) { // .length of function is 2
+      'use strict';
+      if (target == null) { // TypeError if undefined or null
+        throw new TypeError('Cannot convert undefined or null to object');
+      }
+
+      var to = Object(target);
+
+      for (var index = 1; index < arguments.length; index++) {
+        var nextSource = arguments[index];
+
+        if (nextSource != null) { // Skip over if undefined or null
+          for (var nextKey in nextSource) {
+            // Avoid bugs when hasOwnProperty is shadowed
+            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+              to[nextKey] = nextSource[nextKey];
+            }
+          }
+        }
+      }
+      return to;
+    },
+    writable: true,
+    configurable: true
+  });
+}
+
+if (!String.prototype.repeat) {
+  String.prototype.repeat = function(count) {
+    'use strict';
+    if (this == null) {
+      throw new TypeError('can\'t convert ' + this + ' to object');
+    }
+    var str = '' + this;
+    count = +count;
+    if (count != count) {
+      count = 0;
+    }
+    if (count < 0) {
+      throw new RangeError('repeat count must be non-negative');
+    }
+    if (count == Infinity) {
+      throw new RangeError('repeat count must be less than infinity');
+    }
+    count = Math.floor(count);
+    if (str.length == 0 || count == 0) {
+      return '';
+    }
+    // Ensuring count is a 31-bit integer allows us to heavily optimize the
+    // main part. But anyway, most current (August 2014) browsers can't handle
+    // strings 1 << 28 chars or longer, so:
+    if (str.length * count >= 1 << 28) {
+      throw new RangeError('repeat count must not overflow maximum string size');
+    }
+    var rpt = '';
+    for (var i = 0; i < count; i++) {
+      rpt += str;
+    }
+    return rpt;
+  }
+}
+
+if (!Array.prototype.move) {
+  Array.prototype.move = function(from, to) {
+      this.splice(to, 0, this.splice(from, 1)[0]);
+  };
+}
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
+if (!Array.prototype.findIndex) {
+  Object.defineProperty(Array.prototype, 'findIndex', {
+    value: function(predicate) {
+     // 1. Let O be ? ToObject(this value).
+      if (this == null) {
+        throw new TypeError('"this" is null or not defined');
+      }
+
+      var o = Object(this);
+
+      // 2. Let len be ? ToLength(? Get(O, "length")).
+      var len = o.length >>> 0;
+
+      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+      if (typeof predicate !== 'function') {
+        throw new TypeError('predicate must be a function');
+      }
+
+      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
+      var thisArg = arguments[1];
+
+      // 5. Let k be 0.
+      var k = 0;
+
+      // 6. Repeat, while k < len
+      while (k < len) {
+        // a. Let Pk be ! ToString(k).
+        // b. Let kValue be ? Get(O, Pk).
+        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
+        // d. If testResult is true, return k.
+        var kValue = o[k];
+        if (predicate.call(thisArg, kValue, k, o)) {
+          return k;
+        }
+        // e. Increase k by 1.
+        k++;
+      }
+
+      // 7. Return -1.
+      return -1;
+    }
+  });
+}
\ 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