You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/10/08 13:28:31 UTC

[1/7] incubator-brooklyn git commit: Prevent infinite expand-all of recursive group members.

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 7b89b6644 -> fa131b105


Prevent infinite expand-all of recursive group members.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5ada3e21
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5ada3e21
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5ada3e21

Branch: refs/heads/master
Commit: 5ada3e2135bcd0f175bf5c251c4b3f35b28c32fb
Parents: c9f45a3
Author: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Authored: Wed Oct 7 11:35:09 2015 +0100
Committer: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Committed: Wed Oct 7 14:33:57 2015 +0100

----------------------------------------------------------------------
 .../webapp/assets/js/view/application-tree.js   | 29 ++++++++++++++------
 1 file changed, 21 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ada3e21/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
index 1d31953..2f7a3d0 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
@@ -282,10 +282,11 @@ define([
             return false;
         },
 
-        showChildrenOf: function($treeBox, recurse) {
+        showChildrenOf: function($treeBox, recurse, excludedEntityIds) {
+            excludedEntityIds = excludedEntityIds || [];
+            var idToExpand = $treeBox.data('entityId');
             var $wrapper = $treeBox.children('.entity_tree_node_wrapper');
             var $childContainer = $treeBox.children('.node-children');
-            var idToExpand = $treeBox.data('entityId');
             var model = this.collection.get(idToExpand);
             if (model == null) {
                 // not yet loaded; parallel thread should load
@@ -311,16 +312,28 @@ define([
                 }
             });
 
+            // Avoid infinite recursive expansion using a "taboo list" of indirect entities already expanded in this
+            // operation. Example: a group that contains itself or one of its own ancestors. Such cycles can only
+            // originate via "indirect" subordinates.
+            var expandIfNotExcluded = function($treebox, excludedEntityIds, defer) {
+                if ($treebox.hasClass('indirect')) {
+                    var id = $treebox.data('entityId');
+                    if (_.contains(excludedEntityIds, id))
+                        return;
+                    excludedEntityIds.push(id);
+                }
+                var doExpand = function() { that.showChildrenOf($treebox, recurse, excludedEntityIds); };
+                if (defer) _.defer(doExpand);
+                else doExpand();
+            };
+
             if (this.collection.includeEntities(_.union(children, members))) {
                 // we have to load entities before we can proceed
                 this.collection.fetch({
                     success: function() {
                         if (recurse) {
                             $childContainer.children('.tree-box').each(function () {
-                                var $treebox = $(this);
-                                _.defer(function() {
-                                    that.showChildrenOf($treebox, recurse);
-                                });
+                                expandIfNotExcluded($(this), excludedEntityIds, true);
                             });
                         }
                     }
@@ -331,8 +344,8 @@ define([
             $wrapper.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
             if (recurse) {
                 $childContainer.children('.tree-box').each(function () {
-                    that.showChildrenOf($(this), recurse);
-                })
+                    expandIfNotExcluded($(this), excludedEntityIds, false);
+                });
             }
         },
 


[4/7] incubator-brooklyn git commit: Decouple application-tree view from entity-details view.

Posted by he...@apache.org.
Decouple application-tree view from entity-details view.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/c9f45a32
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/c9f45a32
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/c9f45a32

Branch: refs/heads/master
Commit: c9f45a32f8a85665abd8f8f5fcf7610d7f25f10a
Parents: 345daaa
Author: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Authored: Thu Oct 1 11:39:12 2015 +0100
Committer: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Committed: Wed Oct 7 14:33:57 2015 +0100

----------------------------------------------------------------------
 .../assets/js/view/application-explorer.js      | 120 +++++++++++++--
 .../webapp/assets/js/view/application-tree.js   | 154 +++----------------
 2 files changed, 129 insertions(+), 145 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c9f45a32/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js b/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
index 45160cb..e9c23bc 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
@@ -23,21 +23,24 @@
  */
 define([
     "underscore", "jquery", "backbone", "view/viewutils", 
-    "./application-add-wizard", "model/app-tree", "./application-tree", 
-    "text!tpl/apps/page.html"
-], function (_, $, Backbone, ViewUtils, AppAddWizard, AppTree, ApplicationTreeView, PageHtml) {
+    "./application-add-wizard", "model/application", "model/entity-summary", "model/app-tree", "./application-tree",  "./entity-details",
+    "text!tpl/apps/details.html", "text!tpl/apps/entity-not-found.html", "text!tpl/apps/page.html"
+], function (_, $, Backbone, ViewUtils,
+        AppAddWizard, Application, EntitySummary, AppTree, ApplicationTreeView, EntityDetailsView,
+        EntityDetailsEmptyHtml, EntityNotFoundHtml, PageHtml) {
 
     var ApplicationExplorerView = Backbone.View.extend({
         tagName:"div",
         className:"container container-fluid",
         id:'application-explorer',
         template:_.template(PageHtml),
+        notFoundTemplate: _.template(EntityNotFoundHtml),
         events:{
             'click .application-tree-refresh': 'refreshApplicationsInPlace',
             'click #add-new-application':'createApplication',
             'click .delete':'deleteApplication'
         },
-        initialize:function () {
+        initialize: function () {
             this.$el.html(this.template({}))
             $(".nav1").removeClass("active");
             $(".nav1_apps").addClass("active");
@@ -46,7 +49,12 @@ define([
                 collection:this.collection,
                 appRouter:this.options.appRouter
             })
+            this.treeView.on('entitySelected', function(e) {
+               this.displayEntityId(e.id, e.get('applicationId'), false);
+            }, this);
             this.$('div#app-tree').html(this.treeView.renderFull().el)
+            this.$('div#details').html(EntityDetailsEmptyHtml);
+
             ViewUtils.fetchRepeatedlyWithDelay(this, this.collection)
         },
         refreshApplicationsInPlace: function() {
@@ -54,9 +62,11 @@ define([
             // (not a full visual recompute, which reset does - both in application-tree.js)
             this.collection.fetch();
         },
-        beforeClose:function () {
-            this.collection.off("reset", this.render)
-            this.treeView.close()
+        beforeClose: function () {
+            this.collection.off("reset", this.render);
+            this.treeView.close();
+            if (this.detailsView)
+                this.detailsView.close();
         },
         show: function(entityId) {
             var tab = "";
@@ -79,10 +89,6 @@ define([
             }
             this.treeView.selectEntity(entityId)
         },
-        preselectTab: function(tab, tabDetails) {
-            this.treeView.preselectTab(tab, tabDetails)
-        },
-        
         createApplication:function () {
             var that = this;
             if (this._modal) {
@@ -102,7 +108,97 @@ define([
         deleteApplication:function (event) {
             // call Backbone destroy() which does HTTP DELETE on the model
             this.collection.get(event.currentTarget['id']).destroy({wait:true})
-        }
+        },
+        /**
+         * Causes the tab with the given name to be selected automatically when
+         * the view is next rendered.
+         */
+        preselectTab: function(tab, tabDetails) {
+            this.currentTab = tab;
+            this.currentTabDetails = tabDetails;
+        },
+        showDetails: function(app, entitySummary) {
+            var that = this;
+            ViewUtils.cancelFadeOnceLoaded($("div#details"));
+
+            var whichTab = this.currentTab;
+            if (!whichTab) {
+                whichTab = "summary";
+                if (this.detailsView) {
+                    whichTab = this.detailsView.$el.find(".tab-pane.active").attr("id");
+                    this.detailsView.close();
+                }
+            }
+            if (this.detailsView) {
+                this.detailsView.close();
+            }
+            this.detailsView = new EntityDetailsView({
+                model: entitySummary,
+                application: app,
+                appRouter: this.options.appRouter,
+                preselectTab: whichTab,
+                preselectTabDetails: this.currentTabDetails,
+            });
+
+            this.detailsView.on("entity.expunged", function() {
+                that.preselectTab("summary");
+                var id = that.selectedEntityId;
+                var model = that.collection.get(id);
+                if (model && model.get("parentId")) {
+                    that.displayEntityId(model.get("parentId"));
+                } else if (that.collection) {
+                    that.displayEntityId(that.collection.first().id);
+                } else if (id) {
+                    that.displayEntityNotFound(id);
+                } else {
+                    that.displayEntityNotFound("?");
+                }
+                that.collection.fetch();
+            });
+            this.detailsView.render( $("div#details") );
+        },
+        displayEntityId: function (id, appName, afterLoad) {
+            var that = this;
+            var entityLoadFailed = function() {
+                return that.displayEntityNotFound(id);
+            };
+            if (appName === undefined) {
+                if (!afterLoad) {
+                    // try a reload if given an ID we don't recognise
+                    this.collection.includeEntities([id]);
+                    this.collection.fetch({
+                        success: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); },
+                        error: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); }
+                    });
+                    ViewUtils.fadeToIndicateInitialLoad($("div#details"))
+                    return;
+                } else {
+                    // no such app
+                    entityLoadFailed();
+                    return; 
+                }
+            }
+
+            var app = new Application.Model();
+            var entitySummary = new EntitySummary.Model;
+
+            app.url = "/v1/applications/" + appName;
+            entitySummary.url = "/v1/applications/" + appName + "/entities/" + id;
+
+            // in case the server response time is low, fade out while it refreshes
+            // (since we can't show updated details until we've retrieved app + entity details)
+            ViewUtils.fadeToIndicateInitialLoad($("div#details"));
+
+            $.when(app.fetch(), entitySummary.fetch())
+                .done(function() {
+                    that.showDetails(app, entitySummary);
+                })
+                .fail(entityLoadFailed);
+        },
+        displayEntityNotFound: function(id) {
+            $("div#details").html(this.notFoundTemplate({"id": id}));
+            ViewUtils.cancelFadeOnceLoaded($("div#details"))
+        },
     })
 
     return ApplicationExplorerView

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/c9f45a32/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
index be21e40..1d31953 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
@@ -22,14 +22,12 @@
  */
 define([
     "underscore", "jquery", "backbone", "view/viewutils",
-    "model/app-tree", "./entity-details", "model/entity-summary", "model/application",
-    "text!tpl/apps/tree-item.html", "text!tpl/apps/tree-empty.html", "text!tpl/apps/details.html", "text!tpl/apps/entity-not-found.html"
+    "model/app-tree", "text!tpl/apps/tree-item.html", "text!tpl/apps/tree-empty.html"
 ], function (_, $, Backbone, ViewUtils,
-             AppTree, EntityDetailsView, EntitySummary, Application,
-             TreeItemHtml, TreeEmptyHtml, EntityDetailsEmptyHtml, EntityNotFoundHtml) {
+             AppTree, TreeItemHtml, EmptyTreeHtml) {
 
-    var treeViewTemplate = _.template(TreeItemHtml);
-    var notFoundTemplate = _.template(EntityNotFoundHtml);
+    var emptyTreeTemplate = _.template(EmptyTreeHtml);
+    var treeItemTemplate = _.template(TreeItemHtml);
 
     var findAllTreeboxes = function(id, $scope) {
         return $('.tree-box[data-entity-id="' + id + '"]', $scope);
@@ -43,8 +41,8 @@ define([
         return $parentTreebox.children('.node-children').children('.tree-box[data-entity-id="' + id + '"]');
     };
 
-    var findMasterTreebox = function(id) {
-        return $('.tree-box[data-entity-id="' + id + '"]:not(.indirect)');
+    var findMasterTreebox = function(id, $scope) {
+        return $('.tree-box[data-entity-id="' + id + '"]:not(.indirect)', $scope);
     };
 
     var createEntityTreebox = function(id, name, $domParent, depth, indirect) {
@@ -153,13 +151,13 @@ define([
         var entity = treeView.collection.get(id);
         if (entity) {
             treeView.selectedEntityId = id;
-            treeView.displayEntityId(entity.id, entity.get('applicationId'), false);
+            treeView.trigger('entitySelected', entity);
         }
     };
 
 
     return Backbone.View.extend({
-        template: treeViewTemplate,
+        template: treeItemTemplate,
         hoverTimer: null,
 
         events: {
@@ -177,7 +175,6 @@ define([
 
         beforeClose: function() {
             this.collection.off("reset", this.renderFull);
-            if (this.detailsView) this.detailsView.close();
         },
 
         entityAdded: function(entity) {
@@ -187,7 +184,11 @@ define([
 
             // If the new entity is an application, we must create its placeholder in the DOM.
             if (!entity.get('parentId')) {
-                getOrCreateApplicationTreebox(entity.id, entity.get('name'), this);
+                var $treebox = getOrCreateApplicationTreebox(entity.id, entity.get('name'), this);
+
+                // Select the new app if there's no current selection.
+                if (!this.selectedEntityId)
+                    selectTreebox(entity.id, $treebox, this);
             }
 
             this.entityChanged(entity);
@@ -219,7 +220,7 @@ define([
         },
 
         selectEntity: function(id) {
-            var $treebox = findMasterTreebox(id);
+            var $treebox = findMasterTreebox(id, this.$el);
             selectTreebox(id, $treebox, this);
         },
 
@@ -229,7 +230,8 @@ define([
 
             // Display tree and highlight the selected entity.
             if (this.collection.getApplications().length == 0) {
-                that.$el.append(_.template(TreeEmptyHtml));
+                this.$el.append(emptyTreeTemplate());
+
             } else {
                 _.each(this.collection.getApplications(), function(appId) {
                     var entity = that.collection.get(appId);
@@ -238,24 +240,11 @@ define([
                 });
             }
 
-            // this.highlightEntity();
-
-            // Render the details for the selected entity.
-            if (this.detailsView) {
-                this.detailsView.render();
-            } else {
-                // if nothing selected, select the first application
-                if (!this.collection.isEmpty()) {
-                    var app0 = this.collection.first().id;
-                    _.defer(function () {
-                        that.selectEntity(app0);
-                    });
-                } else {
-                    _.defer(function() {
-                        $("div#details").html(_.template(EntityDetailsEmptyHtml));
-                        $("div#details").find("a[href='#summary']").tab('show');
-                    });
-                }
+            // Select the first app if there's no current selection.
+            if (!this.selectedEntityId) {
+                var firstApp = _.first(this.collection.getApplications());
+                if (firstApp)
+                    this.selectEntity(firstApp);
             }
             return this;
         },
@@ -267,57 +256,6 @@ define([
             }
         },
 
-        displayEntityId: function (id, appName, afterLoad) {
-            var that = this;
-            //this.highlightEntity(id);
-
-            var entityLoadFailed = function() {
-                return that.displayEntityNotFound(id);
-            };
-
-            if (appName === undefined) {
-                var $treebox = findMasterTreebox(id);
-                appName = $treebox.children(".entity_tree_node").data("app-id");
-            }
-            if (appName === undefined) {
-                if (!afterLoad) {
-                    // try a reload if given an ID we don't recognise
-                    this.collection.includeEntities([id]);
-                    this.collection.fetch({
-                        success: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); },
-                        error: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); }
-                    });
-                    ViewUtils.fadeToIndicateInitialLoad($("div#details"))
-                    return;
-                } else {
-                    // no such app
-                    entityLoadFailed();
-                    return; 
-                }
-            }
-
-            var app = new Application.Model();
-            var entitySummary = new EntitySummary.Model;
-
-            app.url = "/v1/applications/" + appName;
-            entitySummary.url = "/v1/applications/" + appName + "/entities/" + id;
-
-            // in case the server response time is low, fade out while it refreshes
-            // (since we can't show updated details until we've retrieved app + entity details)
-            ViewUtils.fadeToIndicateInitialLoad($("div#details"));
-
-            $.when(app.fetch(), entitySummary.fetch())
-                .done(function() {
-                    that.showDetails(app, entitySummary);
-                })
-                .fail(entityLoadFailed);
-        },
-
-        displayEntityNotFound: function(id) {
-            $("div#details").html(notFoundTemplate({"id": id}));
-            ViewUtils.cancelFadeOnceLoaded($("div#details"))
-        },
-
         treeChange: function(event) {
             var $target = $(event.currentTarget);
             var $treeBox = $target.closest('.tree-box');
@@ -411,56 +349,6 @@ define([
             $wrapper.find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right');
         },
 
-        /**
-         * Causes the tab with the given name to be selected automatically when
-         * the view is next rendered.
-         */
-        preselectTab: function(tab, tabDetails) {
-            this.currentTab = tab;
-            this.currentTabDetails = tabDetails;
-        },
-
-        showDetails: function(app, entitySummary) {
-            var that = this;
-            ViewUtils.cancelFadeOnceLoaded($("div#details"));
-
-            var whichTab = this.currentTab;
-            if (whichTab === undefined) {
-                whichTab = "summary";
-                if (this.detailsView) {
-                    whichTab = this.detailsView.$el.find(".tab-pane.active").attr("id");
-                    this.detailsView.close();
-                }
-            }
-            if (this.detailsView) {
-                this.detailsView.close();
-            }
-            this.detailsView = new EntityDetailsView({
-                model: entitySummary,
-                application: app,
-                appRouter: this.options.appRouter,
-                preselectTab: whichTab,
-                preselectTabDetails: this.currentTabDetails,
-            });
-
-            this.detailsView.on("entity.expunged", function() {
-                that.preselectTab("summary");
-                var id = that.selectedEntityId;
-                var model = that.collection.get(id);
-                if (model && model.get("parentId")) {
-                    that.displayEntityId(model.get("parentId"));
-                } else if (that.collection) {
-                    that.displayEntityId(that.collection.first().id);
-                } else if (id) {
-                    that.displayEntityNotFound(id);
-                } else {
-                    that.displayEntityNotFound("?");
-                }
-                that.collection.fetch();
-            });
-            this.detailsView.render( $("div#details") );
-        },
-
     });
 
 });


[7/7] incubator-brooklyn git commit: This closes #942

Posted by he...@apache.org.
This closes #942


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/fa131b10
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/fa131b10
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/fa131b10

Branch: refs/heads/master
Commit: fa131b1055398f4caa7479d42ed8d3e3679ba9ea
Parents: e23889c b02bd17
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 8 12:28:06 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Oct 8 12:28:06 2015 +0100

----------------------------------------------------------------------
 .../webapp/assets/tpl/app-add-wizard/required-config-entry.html    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------



[6/7] incubator-brooklyn git commit: This closes #929

Posted by he...@apache.org.
This closes #929


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/e23889cc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/e23889cc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/e23889cc

Branch: refs/heads/master
Commit: e23889cc63745b3f542b2397272a86307ce1894c
Parents: 7b89b66 5ada3e2
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Oct 8 12:27:57 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Thu Oct 8 12:27:57 2015 +0100

----------------------------------------------------------------------
 usage/jsgui/src/main/webapp/assets/css/base.css |   9 +
 .../src/main/webapp/assets/js/model/app-tree.js |  62 +-
 .../assets/js/view/application-explorer.js      | 122 +++-
 .../webapp/assets/js/view/application-tree.js   | 700 ++++++++-----------
 .../webapp/assets/js/view/entity-summary.js     |   2 +-
 .../main/webapp/assets/tpl/apps/summary.html    |   6 +-
 .../main/webapp/assets/tpl/apps/tree-item.html  |  26 +-
 7 files changed, 448 insertions(+), 479 deletions(-)
----------------------------------------------------------------------



[2/7] incubator-brooklyn git commit: Show group members as ‘indirect’ subordinates in tree.

Posted by he...@apache.org.
Show group members as ‘indirect’ subordinates in tree.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/345daaa0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/345daaa0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/345daaa0

Branch: refs/heads/master
Commit: 345daaa0050b1dbe2fea536a566f6552227294fc
Parents: 99c0119
Author: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Authored: Thu Oct 1 08:49:51 2015 +0100
Committer: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Committed: Wed Oct 7 14:33:57 2015 +0100

----------------------------------------------------------------------
 usage/jsgui/src/main/webapp/assets/css/base.css |   9 +
 .../assets/js/view/application-explorer.js      |   2 +-
 .../webapp/assets/js/view/application-tree.js   | 506 +++++++++----------
 .../main/webapp/assets/tpl/apps/tree-item.html  |  12 +-
 4 files changed, 246 insertions(+), 283 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/345daaa0/usage/jsgui/src/main/webapp/assets/css/base.css
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/css/base.css b/usage/jsgui/src/main/webapp/assets/css/base.css
index 04a488d..a80f35b 100644
--- a/usage/jsgui/src/main/webapp/assets/css/base.css
+++ b/usage/jsgui/src/main/webapp/assets/css/base.css
@@ -580,6 +580,15 @@ ol.tree {
 .entity_tree_node_wrapper.active .entity_tree_node {
     font-weight: bold;
 }
+.entity_tree_node_wrapper .indirection-icon {
+    opacity: 0.7;
+    margin-left: -5px;
+}
+.tree-box.indirect > .entity_tree_node_wrapper a {
+    font-style: italic !important;
+    color: #666;
+}
+
 #tree label {
 /* remove the folder, and align with + - icons */
 background: none;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/345daaa0/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js b/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
index b7d6f70..45160cb 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
@@ -77,7 +77,7 @@ define([
                 }
                 this.preselectTab(tab, tabDetails);
             }
-            this.treeView.displayEntityId(entityId)
+            this.treeView.selectEntity(entityId)
         },
         preselectTab: function(tab, tabDetails) {
             this.treeView.preselectTab(tab, tabDetails)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/345daaa0/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
index 8a5175a..be21e40 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
@@ -31,22 +31,147 @@ define([
     var treeViewTemplate = _.template(TreeItemHtml);
     var notFoundTemplate = _.template(EntityNotFoundHtml);
 
+    var findAllTreeboxes = function(id, $scope) {
+        return $('.tree-box[data-entity-id="' + id + '"]', $scope);
+    };
+
+    var findRootTreebox = function(id) {
+        return $('.lozenge-app-tree-wrapper').children('.tree-box[data-entity-id="' + id + '"]', this.$el);
+    };
+
+    var findChildTreebox = function(id, $parentTreebox) {
+        return $parentTreebox.children('.node-children').children('.tree-box[data-entity-id="' + id + '"]');
+    };
+
+    var findMasterTreebox = function(id) {
+        return $('.tree-box[data-entity-id="' + id + '"]:not(.indirect)');
+    };
+
+    var createEntityTreebox = function(id, name, $domParent, depth, indirect) {
+        // Tildes in sort key force entities with no name to bottom of list (z < ~).
+        var sortKey = (name ? name.toLowerCase() : "~~~") + "     " + id.toLowerCase();
+
+        // Create the wrapper.
+        var $treebox = $(
+                '<div data-entity-id="'+id+'" data-sort-key="'+sortKey+'" data-depth="'+depth+'" ' +
+                'class="tree-box toggler-group' +
+                    (indirect ? " indirect" : "") +
+                    (depth == 0 ? " outer" : " inner " + (depth % 2 ? " depth-odd" : " depth-even")+
+                    (depth == 1 ? " depth-first" : "")) + '">'+
+                '<div class="entity_tree_node_wrapper"></div>'+
+                '<div class="node-children toggler-target hide"></div>'+
+                '</div>');
+
+        // Insert into the passed DOM parent, maintaining sort order relative to siblings: name then id.
+        var placed = false;
+        var contender = $(".toggler-group", $domParent).first();
+        while (contender.length && !placed) {
+            var contenderKey = contender.data("sort-key");
+            if (sortKey < contenderKey) {
+                contender.before($treebox);
+                placed = true;
+            } else {
+                contender = contender.next(".toggler-group", $domParent);
+            }
+        }
+        if (!placed) {
+            $domParent.append($treebox);
+        }
+        return $treebox;
+    };
+
+    var getOrCreateApplicationTreebox = function(id, name, treeView) {
+        var $treebox = findRootTreebox(id);
+        if (!$treebox.length) {
+            var $insertionPoint = $('.lozenge-app-tree-wrapper', treeView.$el);
+            if (!$insertionPoint.length) {
+                // entire view must be created
+                treeView.$el.html(
+                        '<div class="navbar_main_wrapper treeloz">'+
+                        '<div id="tree-list" class="navbar_main treeloz">'+
+                        '<div class="lozenge-app-tree-wrapper">'+
+                        '</div></div></div>');
+                $insertionPoint = $('.lozenge-app-tree-wrapper', treeView.$el);
+            }
+            $treebox = createEntityTreebox(id, name, $insertionPoint, 0, false);
+        }
+        return $treebox;
+    };
+
+    var getOrCreateChildTreebox = function(id, name, isIndirect, $parentTreebox) {
+        var $treebox = findChildTreebox(id, $parentTreebox);
+        if (!$treebox.length) {
+            $treebox = createEntityTreebox(id, name, $parentTreebox.children('.node-children'), $parentTreebox.data("depth") + 1, isIndirect);
+        }
+        return $treebox;
+    };
+
+    var updateTreeboxContent = function(entity, $treebox, treeView) {
+        var $newContent = $(treeView.template({
+            id: entity.get('id'),
+            parentId:  entity.get('parentId'),
+            model: entity,
+            statusIconUrl: ViewUtils.computeStatusIconInfo(entity.get("serviceUp"), entity.get("serviceState")).url,
+            indirect: $treebox.hasClass('indirect'),
+        }));
+
+        var $wrapper = $treebox.children('.entity_tree_node_wrapper');
+
+        // Preserve old display status (just chevron direction at present).
+        if ($wrapper.find('.tree-node-state').hasClass('icon-chevron-down')) {
+            $newContent.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
+        }
+
+        $wrapper.html($newContent);
+        addEventsToNode($treebox, treeView);
+    };
+
+    var addEventsToNode = function($node, treeView) {
+        // show the "light-popup" (expand / expand all / etc) menu
+        // if user hovers for 500ms. surprising there is no option for this (hover delay).
+        // also, annoyingly, clicks around the time the animation starts don't seem to get handled
+        // if the click is in an overlapping reason; this is why we position relative top: 12px in css
+        $('.light-popup', $node).parent().parent().hover(
+                function(parent) {
+                    treeView.cancelHoverTimer();
+                    treeView.hoverTimer = setTimeout(function() {
+                        var menu = $(parent.currentTarget).find('.light-popup');
+                        menu.show();
+                    }, 500);
+                },
+                function(parent) {
+                    treeView.cancelHoverTimer();
+                    $('.light-popup').hide();
+                }
+        );
+    };
 
-    var ApplicationTreeView = Backbone.View.extend({
+    var selectTreebox = function(id, $treebox, treeView) {
+        $('.entity_tree_node_wrapper').removeClass('active');
+        $treebox.children('.entity_tree_node_wrapper').addClass('active');
+
+        var entity = treeView.collection.get(id);
+        if (entity) {
+            treeView.selectedEntityId = id;
+            treeView.displayEntityId(entity.id, entity.get('applicationId'), false);
+        }
+    };
+
+
+    return Backbone.View.extend({
         template: treeViewTemplate,
         hoverTimer: null,
 
         events: {
-            'click span.entity_tree_node .tree-change':'treeChange',
-            'click span.entity_tree_node':'displayEntity'
+            'click span.entity_tree_node .tree-change': 'treeChange',
+            'click span.entity_tree_node': 'nodeClicked'
         },
-            this.collection.on('all', this.modelEvent, this)
-            this.collection.on('change', this.modelChange, this)
-            this.collection.on('remove', this.modelRemove, this)
-            this.collection.on('add', this.modelAdd, this)
-            this.collection.on('reset', this.renderFull, this)
 
         initialize: function() {
+            this.collection.on('add', this.entityAdded, this);
+            this.collection.on('change', this.entityChanged, this);
+            this.collection.on('remove', this.entityRemoved, this);
+            this.collection.on('reset', this.renderFull, this);
             _.bindAll(this);
         },
 
@@ -54,171 +179,48 @@ define([
             this.collection.off("reset", this.renderFull);
             if (this.detailsView) this.detailsView.close();
         },
-        
-        modelChange: function (child) {
-            this.updateNode(child.id)
-        },
-        modelAdd: function (child) {
-            this.updateNode(child.id)
-        },
-        modelRemove: function (child) {
-            this.removeNode(child.id)
-        },
 
-        modelEvent: function(eventName, event, x) {
-            if (/^change/i.test(eventName) || eventName == "remove" || eventName == "add" ||
-                    eventName == "reset" ||
-                    // above are handled; below is no-op
-                    eventName == "sync" || eventName == "request")
-                return;
+        entityAdded: function(entity) {
+            // Called when the full entity model is fetched into our collection, at which time we can replace
+            // the empty contents of any placeholder tree nodes (.tree-box) that were created earlier.
+            // The entity may have multiple 'treebox' views (in the case of group members).
 
-            if (eventName == "error") {
-                log("model error in application-tree - has the internet vanished?")
-                // ignore; app-explorer should clear the view
-                return;
+            // If the new entity is an application, we must create its placeholder in the DOM.
+            if (!entity.get('parentId')) {
+                getOrCreateApplicationTreebox(entity.id, entity.get('name'), this);
             }
 
-            // don't think we get other events, but just in case:
-            log("unhandled model event");
-            log(eventName);
-            log(event);
-            log(x);
+            this.entityChanged(entity);
         },
 
-        removeNode: function(id) {
-            $('#'+id, this.$el).parent().remove()
-            // collection seems sometimes to have children nodes;
-            // not sure why, but that's okay for now
-            if (this.collection.getApplications().length==0)
-                this.renderFull();
-        },
-        
-        updateNode: function(id, parentId, isApp) {
+        entityChanged: function(entity) {
+            // The entity may have multiple 'treebox' views (in the case of group members).
             var that = this;
-            var nModel = that.collection.get(id);
-            var node = $('#'+id, that.$el)
-            
-            if (!isApp) {
-                // autodiscover whether this is an app, looking at the model and the tree
-                // (at least one should be available -- probably always the former, but...)
-                if (nModel) { isApp = (id == nModel.get('applicationId')); }
-                else if (!isApp && node && node.parent().data('depth')==0) isApp = true;
-            }
+            findAllTreeboxes(entity.id).each(function() {
+                var $treebox = $(this);
+                updateTreeboxContent(entity, $treebox, that);
+            });
+        },
 
-            if (!isApp && !parentId && nModel)
-                parentId = nModel.get('parentId');
-            if (!isApp && !parentId && node)
-                parentId = node.closest("entity_tree_node_wrapper").data('parentId');
-            if (!isApp && !parentId) {
-                log("no parentId yet available for "+id+"; skipping;")
-                return false;
-            }
-            
-            var statusIconUrl = nModel
-                ? ViewUtils.computeStatusIconInfo(nModel.get("serviceUp"),nModel.get("serviceState")).url
-                : null;
-
-            var newNode = this.template({
-                id:id,
-                parentId:parentId,
-                model:nModel,
-                statusIconUrl:statusIconUrl
-            })
-
-            if (!node.length) {
-                // node does not exist, so add it
-                var parentsChildren, depth;
-                
-                if (isApp) {
-                    parentsChildren = $('.lozenge-app-tree-wrapper', that.$el);
-                    if (!parentsChildren.length) {
-                        // entire view must be created
-                        that.$el.html(
-                                '<div class="navbar_main_wrapper treeloz">'+
-                                '<div id="tree-list" class="navbar_main treeloz">'+
-                                '<div class="lozenge-app-tree-wrapper">'+
-                                '</div></div></div>');
-                        parentsChildren = $('.lozenge-app-tree-wrapper', that.$el);
-                    }
-                    depth = 0;
-                } else {
-                    var parent = $('#'+parentId, that.$el)
-                    if (!parent.length) {
-                        // see if we can load the parent
-                        if (this.updateNode(parentId)) {
-                            parent = $('#'+parentId, that.$el);
-                            if (!parent.length) {
-                                log("no parent element yet available for "+id+" ("+parentId+") after parent load; skipping")
-                                return false;                                
-                            }
-                        } else {
-                            log("no parent element yet available for "+id+" ("+parentId+"); skipping")
-                            return false;
-                        }
-                    }
-                    parentsChildren = $(parent.parent().children('.node-children'));
-                    depth = parent.parent().data("depth")+1
-                }
+        entityRemoved: function(entity) {
+            // The entity may have multiple 'treebox' views (in the case of group members).
+            findAllTreeboxes(entity.id, this.$el).remove();
+            // Collection seems sometimes to retain children of the removed node;
+            // not sure why, but that's okay for now.
+            if (this.collection.getApplications().length == 0)
+                this.renderFull();
+        },
 
-                // add it, with surrounding html, in parent's node-children child
-                // tildes in sortKey force entities with no name to bottom of list (z < ~).
-                var entityName = nModel && nModel.get("name")
-                        ? nModel.get("name")
-                        : this.collection.getEntityNameFromId(id);
-                var sortKey = (entityName ? entityName.toLowerCase() : "~~~") + "     " + id.toLowerCase();
-                var newNodeWrapper = $(
-                        '<div data-sort-key="'+sortKey+'" class="toggler-group tree-box '+
-                            (depth==0 ? "outer" : "inner "+(depth%2==1 ? "depth-odd" : "depth-even")+
-                                (depth==1 ? " depth-first" : "")) + '" data-depth="'+depth+'">'+
-                        '<div id="'+id+'" class="entity_tree_node_wrapper"></div>'+
-                        '<div class="toggler-target hide node-children"></div>'+
-                        '</div>')
-                $('#'+id, newNodeWrapper).html(newNode);
-
-                // Maintain entities sorted by name, then id.
-                var placed = false;
-                var contender = $(".toggler-group", parentsChildren).first();
-                while (contender.length && !placed) {
-                    var contenderKey = contender.data("sort-key");
-                    if (sortKey < contenderKey) {
-                        contender.before(newNodeWrapper);
-                        placed = true;
-                    } else {
-                        contender = contender.next(".toggler-group", parentsChildren);
-                    }
-                }
-                if (!placed) {
-                    parentsChildren.append(newNodeWrapper);
-                }
-                this.addEventsToNode(parentsChildren)
-            } else {
-                // updating
-                var $node = $(node),
-                    $newNode = $(newNode);
-                
-                // preserve old display status (just chevron direction at present)
-                if ($node.find('.tree-node-state').hasClass('icon-chevron-down')) {
-                    $newNode.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down')
-                    // and if visible, see if any children have been added
-                    var children = nModel.get("children");
-                    var newChildren = []
-                    _.each(children, function(child) {
-                        var childId = child.id;
-                        if (!that.collection.get(childId)) {
-                            newChildren.push(childId);
-                        }
-                    })
-                    if (newChildren.length) {
-                        // reload if new child ID we don't recognise
-                        this.collection.includeEntities(newChildren);
-                        this.collection.fetch()
-                    }
-                }
+        nodeClicked: function(event) {
+            var $treebox = $(event.currentTarget).closest('.tree-box');
+            var id = $treebox.data('entityId');
+            selectTreebox(id, $treebox, this);
+            return false;
+        },
 
-                $(node).html($newNode)
-                this.addEventsToNode($(node))
-            }
-            return true;
+        selectEntity: function(id) {
+            var $treebox = findMasterTreebox(id);
+            selectTreebox(id, $treebox, this);
         },
 
         renderFull: function() {
@@ -230,15 +232,13 @@ define([
                 that.$el.append(_.template(TreeEmptyHtml));
             } else {
                 _.each(this.collection.getApplications(), function(appId) {
-                    that.updateNode(appId, null, true);
-                });
-                
-                _.each(this.collection.getNonApplications(), function(id) {
-                    that.updateNode(id);
+                    var entity = that.collection.get(appId);
+                    var $treebox = getOrCreateApplicationTreebox(entity.id, entity.name, that);
+                    updateTreeboxContent(entity, $treebox, that);
                 });
             }
 
-            this.highlightEntity();
+            // this.highlightEntity();
 
             // Render the details for the selected entity.
             if (this.detailsView) {
@@ -248,8 +248,7 @@ define([
                 if (!this.collection.isEmpty()) {
                     var app0 = this.collection.first().id;
                     _.defer(function () {
-                        if (!that.selectedEntityId)
-                            that.displayEntityId(app0, app0);
+                        that.selectEntity(app0);
                     });
                 } else {
                     _.defer(function() {
@@ -261,30 +260,6 @@ define([
             return this;
         },
 
-        addEventsToNode: function($node) {
-            var that = this;
-
-            // show the "light-popup" (expand / expand all / etc) menu
-            // if user hovers for 500ms. surprising there is no option for this (hover delay).
-            // also, annoyingly, clicks around the time the animation starts don't seem to get handled
-            // if the click is in an overlapping reason; this is why we position relative top: 12px in css
-            $('.light-popup', $node).parent().parent().hover(
-                    function(parent) {
-                        that.cancelHoverTimer();
-                        that.hoverTimer = setTimeout(function() {
-                            var menu = $(parent.currentTarget).find('.light-popup')
-                            menu.show()
-                        }, 500);
-                    },
-                    function(parent) {
-                        that.cancelHoverTimer();
-                        var menu = $(parent.currentTarget).find('.light-popup')
-                        menu.hide()
-                        // hide all others too
-                        $('.light-popup').hide()
-                    });
-        },
-
         cancelHoverTimer: function() {
             if (this.hoverTimer != null) {
                 clearTimeout(this.hoverTimer);
@@ -292,42 +267,17 @@ define([
             }
         },
 
-        displayEntity: function(event) {
-            if (event.metaKey || event.shiftKey)
-                // trying to open in a new tab, do not act on it here!
-                return;
-            event.preventDefault();
-            var $nodeSpan = $(event.currentTarget);
-            var $nodeA = $nodeSpan.children('a').first();
-            var entityId = $nodeSpan.closest('.tree-box').data("entityId");
-            var href = $nodeA.attr('href');
-            var tab = (this.detailsView)
-                    ? this.detailsView.$el.find(".tab-pane.active").attr("id")
-                    : undefined;
-            if (href) {
-                if (tab) {
-                    href = href+"/"+tab;
-                    stateId = entityId+"/"+tab;
-                    this.preselectTab(tab);
-                }
-                Backbone.history.navigate(href);
-                this.displayEntityId(entityId, $nodeSpan.data("app-id"));
-            } else {
-                log("no a.href in clicked target");
-                log($nodeSpan);
-            }
-        },
-
         displayEntityId: function (id, appName, afterLoad) {
             var that = this;
-            this.highlightEntity(id);
+            //this.highlightEntity(id);
 
             var entityLoadFailed = function() {
                 return that.displayEntityNotFound(id);
             };
 
             if (appName === undefined) {
-                appName = $("#span-"+id).data("app-id")
+                var $treebox = findMasterTreebox(id);
+                appName = $treebox.children(".entity_tree_node").data("app-id");
             }
             if (appName === undefined) {
                 if (!afterLoad) {
@@ -391,56 +341,76 @@ define([
             this.cancelHoverTimer();
             $('.light-popup').hide();
             // don't let other events interfere
-            return false
-        },
-
-        hideChildrenOf: function($treeBox, recurse) {
-            var that = this;
-            if (recurse) {
-                $treeBox.children('.node-children').children().each(function (index, childBox) {
-                    that.hideChildrenOf($(childBox), recurse)
-                });
-            }
-            $treeBox.children('.node-children').slideUp(300);
-            $treeBox.children('.entity_tree_node_wrapper').find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right');
+            return false;
         },
 
         showChildrenOf: function($treeBox, recurse) {
-            var that = this;
-            var idToExpand = $treeBox.children('.entity_tree_node_wrapper').attr('id');
+            var $wrapper = $treeBox.children('.entity_tree_node_wrapper');
+            var $childContainer = $treeBox.children('.node-children');
+            var idToExpand = $treeBox.data('entityId');
             var model = this.collection.get(idToExpand);
             if (model == null) {
                 // not yet loaded; parallel thread should load
                 return;
             }
-            var children = model.get('children');
+
+            var that = this;
+            var children = model.get('children'); // entity summaries: {id: ..., name: ...}
+            var renderChildrenAsIndirect = $treeBox.hasClass("indirect");
             _.each(children, function(child) {
-                var id = child.id;
-                if (!$('#'+id, that.$el).length)
-                    // load, but only if necessary
-                    that.updateNode(id, idToExpand) 
-            })
-            if (this.collection.includeEntities(children)) {
+                var $treebox = getOrCreateChildTreebox(child.id, child.name, renderChildrenAsIndirect, $treeBox);
+                var model = that.collection.get(child.id);
+                if (model) {
+                    updateTreeboxContent(model, $treebox, that);
+                }
+            });
+            var members = model.get('members'); // entity summaries: {id: ..., name: ...}
+            _.each(members, function(member) {
+                var $treebox = getOrCreateChildTreebox(member.id, member.name, true, $treeBox);
+                var model = that.collection.get(member.id);
+                if (model) {
+                    updateTreeboxContent(model, $treebox, that);
+                }
+            });
+
+            if (this.collection.includeEntities(_.union(children, members))) {
                 // we have to load entities before we can proceed
                 this.collection.fetch({
                     success: function() {
                         if (recurse) {
-                            $treeBox.children('.node-children').children().each(function (index, childBox) {
-                                _.defer( function() { that.showChildrenOf($(childBox), recurse) } );
+                            $childContainer.children('.tree-box').each(function () {
+                                var $treebox = $(this);
+                                _.defer(function() {
+                                    that.showChildrenOf($treebox, recurse);
+                                });
                             });
                         }
                     }
-                })
+                });
             }
-            $treeBox.children('.node-children').slideDown(300);
-            $treeBox.children('.entity_tree_node_wrapper').find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
+
+            $childContainer.slideDown(300);
+            $wrapper.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
             if (recurse) {
-                $treeBox.children('.node-children').children().each(function (index, childBox) {
-                    that.showChildrenOf($(childBox), recurse);
+                $childContainer.children('.tree-box').each(function () {
+                    that.showChildrenOf($(this), recurse);
                 })
             }
         },
 
+        hideChildrenOf: function($treeBox, recurse) {
+            var $wrapper = $treeBox.children('.entity_tree_node_wrapper');
+            var $childContainer = $treeBox.children('.node-children');
+            if (recurse) {
+                var that = this;
+                $childContainer.children('.tree-box').each(function () {
+                    that.hideChildrenOf($(this), recurse);
+                });
+            }
+            $childContainer.slideUp(300);
+            $wrapper.find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right');
+        },
+
         /**
          * Causes the tab with the given name to be selected automatically when
          * the view is next rendered.
@@ -466,11 +436,11 @@ define([
                 this.detailsView.close();
             }
             this.detailsView = new EntityDetailsView({
-                model:entitySummary,
-                application:app,
-                appRouter:this.options.appRouter,
-                preselectTab:whichTab,
-                preselectTabDetails:this.currentTabDetails,
+                model: entitySummary,
+                application: app,
+                appRouter: this.options.appRouter,
+                preselectTab: whichTab,
+                preselectTabDetails: this.currentTabDetails,
             });
 
             this.detailsView.on("entity.expunged", function() {
@@ -491,28 +461,6 @@ define([
             this.detailsView.render( $("div#details") );
         },
 
-        highlightEntity: function(id) {
-            if (id) this.selectedEntityId = id;
-            else id = this.selectedEntityId;
-
-            $(".entity_tree_node_wrapper").removeClass("active");
-            if (id) {
-                var $selectedNode = $(".entity_tree_node_wrapper#"+id);
-                // make this node active
-                $selectedNode.addClass("active");
-
-                // open the parent nodes if needed
-                var $nodeToOpenInParent = $selectedNode;
-                while ($nodeToOpenInParent.length && !$nodeToOpenInParent.is(':visible')) {
-                    $nodeToOpenInParent = $nodeToOpenInParent.closest('.node-children').closest('.tree-box');
-                    this.showChildrenOf($nodeToOpenInParent);
-                }
-
-                // if we want to auto-expand the children of the selected node:
-//              this.showChildrenOf($selectedNode.closest('.tree-box'), false)
-            }
-        }
     });
 
-    return ApplicationTreeView;
-})
+});

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/345daaa0/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
index 88e671a..d1a3d74 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
@@ -22,11 +22,14 @@ under the License.
     var isLoaded = (model ? true : false);
     var isApp = (parentId ? false : true);
 
+    // Only emphasise applications at the top level, not as indirect references.
+    isApp &= !indirect;
+
     if (!isLoaded) {
 %>
         <i>Loading... (<%= id %>)</i>
 <%  } else {
-        var hasChildren = model.hasChildren();
+        var hasChildren = model.hasChildren() || model.hasMembers();
         var iconUrl = model.get('iconUrl');
 
         var entityIconSize = isApp ? 40 : 30;
@@ -38,7 +41,7 @@ under the License.
 %>
 
   <span class="entity_tree_node name entity" id="span-<%= id %>" 
-        data-entity-type="<%= model.get('type') %>" data-parent-id="<%= parentId %>" data-app-id="<%= model.get('applicationId') %>">
+        data-entity-id="<%= id %>" data-entity-type="<%= model.get('type') %>" data-parent-id="<%= parentId %>" data-app-id="<%= model.get('applicationId') %>">
     <a href="#v1/applications/<%= model.get('applicationId') %>/entities/<%= id %>">
 
       <div style="min-width: <%= statusColumnWidth + (iconUrl ? entityIconSize : 6)%>px; min-height: <%= minHeight %>px; max-height: 40px; display: inline-block; margin-right: 4px; vertical-align: middle;">
@@ -68,7 +71,10 @@ under the License.
             <img src="<%= iconUrl %>" style="max-width: <%= entityIconSize %>px; max-height: <%= entityIconSize %>px; position: absolute; left: <%= statusColumnWidth %>px; top: 0; bottom: 0; margin: auto;">
         <% } %>
       </div>
-     
+
+      <% if (indirect) { %>
+        <i class="indirection-icon icon-share-alt"></i>
+      <% } %>
       <span style="max-height: 18px; padding-right: 6px; position: relative; margin: auto; top: 2px; bottom: 0;"><%= model.get('name') %></span>
 
     </a>


[3/7] incubator-brooklyn git commit: Sanitise JS.

Posted by he...@apache.org.
Sanitise JS.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/99c01191
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/99c01191
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/99c01191

Branch: refs/heads/master
Commit: 99c01191fe1aa078838e1e71da5274be6f332d9c
Parents: e948978
Author: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Authored: Tue Sep 29 18:05:39 2015 +0100
Committer: Alasdair Hodge <gi...@alasdairhodge.co.uk>
Committed: Wed Oct 7 14:33:57 2015 +0100

----------------------------------------------------------------------
 .../src/main/webapp/assets/js/model/app-tree.js |  62 +++---
 .../webapp/assets/js/view/application-tree.js   | 213 +++++++++----------
 .../webapp/assets/js/view/entity-summary.js     |   2 +-
 .../main/webapp/assets/tpl/apps/summary.html    |   6 +-
 .../main/webapp/assets/tpl/apps/tree-item.html  |  14 +-
 5 files changed, 153 insertions(+), 144 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99c01191/usage/jsgui/src/main/webapp/assets/js/model/app-tree.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/model/app-tree.js b/usage/jsgui/src/main/webapp/assets/js/model/app-tree.js
index 2cf6c54..67efd91 100644
--- a/usage/jsgui/src/main/webapp/assets/js/model/app-tree.js
+++ b/usage/jsgui/src/main/webapp/assets/js/model/app-tree.js
@@ -20,27 +20,31 @@ define([
     "underscore", "backbone"
 ], function (_, Backbone) {
 
-    var AppTree = {}
+    var AppTree = {};
 
     AppTree.Model = Backbone.Model.extend({
-        defaults:function () {
+        defaults: function() {
             return {
-                id:"",
-                name:"",
-                type:"",
-                iconUrl:"",
-                serviceUp:"",
-                serviceState:"",
-                applicationId:"",
-                parentId:"",
-                children:[]
-            }
+                id: "",
+                name: "",
+                type: "",
+                iconUrl: "",
+                serviceUp: "",
+                serviceState: "",
+                applicationId: "",
+                parentId: "",
+                children: [],
+                members: []
+            };
+        },
+        getDisplayName: function() {
+            return this.get("name");
         },
-        getDisplayName:function () {
-            return this.get("name")
+        hasChildren: function() {
+            return this.get("children").length > 0;
         },
-        hasChildren:function () {
-            return this.get("children").length > 0
+        hasMembers: function() {
+            return this.get("members").length > 0;
         }
     })
 
@@ -48,7 +52,7 @@ define([
         model: AppTree.Model,
         includedEntities: [],
 
-        getApplications: function () {
+        getApplications: function() {
             var entities = [];
             _.each(this.models, function(it) {
                 if (it.get('id') == it.get('applicationId'))
@@ -56,7 +60,8 @@ define([
             });
             return entities;
         },
-        getNonApplications: function () {
+
+        getNonApplications: function() {
             var entities = [];
             _.each(this.models, function(it) {
                 if (it.get('id') != it.get('applicationId'))
@@ -64,20 +69,22 @@ define([
             });
             return entities;
         },
-        includeEntities: function (entities) {
+
+        includeEntities: function(entities) {
             // accepts id as string or object with id field
             var oldLength = this.includedEntities.length;
-            var newList = [].concat(this.includedEntities)
+            var newList = [].concat(this.includedEntities);
             for (entityId in entities) {
-                var entity = entities[entityId]
+                var entity = entities[entityId];
                 if (typeof entity === 'string')
-                    newList.push(entity)
+                    newList.push(entity);
                 else
-                    newList.push(entity.id)
+                    newList.push(entity.id);
             }
-            this.includedEntities = _.uniq(newList)
+            this.includedEntities = _.uniq(newList);
             return (this.includedEntities.length > oldLength);
         },
+
         /**
          * Depth-first search of entries in this.models for the first entity whose ID matches the
          * function's argument. Includes each entity's children.
@@ -88,7 +95,7 @@ define([
             for (var i = 0, l = this.models.length; i < l; i++) {
                 var model = this.models[i];
                 if (model.get("id") === id) {
-                    return model.getDisplayName()
+                    return model.getDisplayName();
                 } else {
                     // slice(0) makes a shallow clone of the array
                     var queue = model.get("children").slice(0);
@@ -109,6 +116,7 @@ define([
             // a string they'll get "stringundefined", whereas this way they'll just get "string".
             return "";
         },
+
         url: function() {
             if (this.includedEntities.length) {
                 var ids = this.includedEntities.join(",");
@@ -116,7 +124,7 @@ define([
             } else
                 return "/v1/applications/fetch";
         }
-    })
+    });
 
-    return AppTree
+    return AppTree;
 })
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99c01191/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
index 6c1c972..8a5175a 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
@@ -28,31 +28,31 @@ define([
              AppTree, EntityDetailsView, EntitySummary, Application,
              TreeItemHtml, TreeEmptyHtml, EntityDetailsEmptyHtml, EntityNotFoundHtml) {
 
-    var treeViewTemplate = _.template(TreeItemHtml),
-        notFoundTemplate = _.template(EntityNotFoundHtml);
+    var treeViewTemplate = _.template(TreeItemHtml);
+    var notFoundTemplate = _.template(EntityNotFoundHtml);
+
 
-    
     var ApplicationTreeView = Backbone.View.extend({
         template: treeViewTemplate,
         hoverTimer: null,
 
-        events:{
+        events: {
             'click span.entity_tree_node .tree-change':'treeChange',
             'click span.entity_tree_node':'displayEntity'
         },
-        
-        initialize:function () {
             this.collection.on('all', this.modelEvent, this)
             this.collection.on('change', this.modelChange, this)
             this.collection.on('remove', this.modelRemove, this)
             this.collection.on('add', this.modelAdd, this)
             this.collection.on('reset', this.renderFull, this)
+
+        initialize: function() {
             _.bindAll(this);
         },
 
-        beforeClose:function () {
-            this.collection.off("reset", this.renderFull)
-            if (this.detailsView) this.detailsView.close()
+        beforeClose: function() {
+            this.collection.off("reset", this.renderFull);
+            if (this.detailsView) this.detailsView.close();
         },
         
         modelChange: function (child) {
@@ -64,7 +64,8 @@ define([
         modelRemove: function (child) {
             this.removeNode(child.id)
         },
-        modelEvent: function (eventName, event, x) {
+
+        modelEvent: function(eventName, event, x) {
             if (/^change/i.test(eventName) || eventName == "remove" || eventName == "add" ||
                     eventName == "reset" ||
                     // above are handled; below is no-op
@@ -76,12 +77,12 @@ define([
                 // ignore; app-explorer should clear the view
                 return;
             }
-            
+
             // don't think we get other events, but just in case:
-            log("unhandled model event")
-            log(eventName)
-            log(event)
-            log(x)
+            log("unhandled model event");
+            log(eventName);
+            log(event);
+            log(x);
         },
 
         removeNode: function(id) {
@@ -89,7 +90,6 @@ define([
             // collection seems sometimes to have children nodes;
             // not sure why, but that's okay for now
             if (this.collection.getApplications().length==0)
-//            if (this.collection.isEmpty() || $('lozenge-app-tree-wrapper').length==0)
                 this.renderFull();
         },
         
@@ -111,7 +111,7 @@ define([
                 parentId = node.closest("entity_tree_node_wrapper").data('parentId');
             if (!isApp && !parentId) {
                 log("no parentId yet available for "+id+"; skipping;")
-                return false;                
+                return false;
             }
             
             var statusIconUrl = nModel
@@ -218,16 +218,16 @@ define([
                 $(node).html($newNode)
                 this.addEventsToNode($(node))
             }
-            return true
+            return true;
         },
-        
-        renderFull:function () {
-            var that = this
-            this.$el.empty()
+
+        renderFull: function() {
+            var that = this;
+            this.$el.empty();
 
             // Display tree and highlight the selected entity.
-            if (this.collection.getApplications().length==0) {
-                that.$el.append(_.template(TreeEmptyHtml))
+            if (this.collection.getApplications().length == 0) {
+                that.$el.append(_.template(TreeEmptyHtml));
             } else {
                 _.each(this.collection.getApplications(), function(appId) {
                     that.updateNode(appId, null, true);
@@ -237,63 +237,59 @@ define([
                     that.updateNode(id);
                 });
             }
-            
+
             this.highlightEntity();
 
             // Render the details for the selected entity.
             if (this.detailsView) {
-                this.detailsView.render()
+                this.detailsView.render();
             } else {
                 // if nothing selected, select the first application
                 if (!this.collection.isEmpty()) {
                     var app0 = this.collection.first().id;
                     _.defer(function () {
                         if (!that.selectedEntityId)
-                            that.displayEntityId(app0, app0)
+                            that.displayEntityId(app0, app0);
                     });
                 } else {
                     _.defer(function() {
                         $("div#details").html(_.template(EntityDetailsEmptyHtml));
-                        $("div#details").find("a[href='#summary']").tab('show')
-                    })
+                        $("div#details").find("a[href='#summary']").tab('show');
+                    });
                 }
             }
-            return this
+            return this;
         },
 
         addEventsToNode: function($node) {
             var that = this;
-            
-            // prevent default click-handling
-            // don't think this is needed, 18 Sep 2013; but leaving for a few weeks just in case
-//            $('a', $node).click(function(e) { e.preventDefault(); })
-            
+
             // show the "light-popup" (expand / expand all / etc) menu
             // if user hovers for 500ms. surprising there is no option for this (hover delay).
             // also, annoyingly, clicks around the time the animation starts don't seem to get handled
             // if the click is in an overlapping reason; this is why we position relative top: 12px in css
             $('.light-popup', $node).parent().parent().hover(
-                    function (parent) {
+                    function(parent) {
                         that.cancelHoverTimer();
                         that.hoverTimer = setTimeout(function() {
                             var menu = $(parent.currentTarget).find('.light-popup')
                             menu.show()
                         }, 500);
                     },
-                    function (parent) {
+                    function(parent) {
                         that.cancelHoverTimer();
                         var menu = $(parent.currentTarget).find('.light-popup')
                         menu.hide()
                         // hide all others too
                         $('.light-popup').hide()
-                    })
+                    });
         },
+
         cancelHoverTimer: function() {
-            var that = this;
-            if (that.hoverTimer!=null) {
-                clearTimeout(that.hoverTimer);
-                that.hoverTimer = null;
-            }            
+            if (this.hoverTimer != null) {
+                clearTimeout(this.hoverTimer);
+                this.hoverTimer = null;
+            }
         },
 
         displayEntity: function(event) {
@@ -301,29 +297,30 @@ define([
                 // trying to open in a new tab, do not act on it here!
                 return;
             event.preventDefault();
-            var nodeSpan = $(event.currentTarget)
-            var nodeA = $(event.currentTarget).children('a').first()
-            var entityId = nodeSpan.parent().attr("id"),
-                href = nodeA.attr('href'),
-                tab = (this.detailsView)
+            var $nodeSpan = $(event.currentTarget);
+            var $nodeA = $nodeSpan.children('a').first();
+            var entityId = $nodeSpan.closest('.tree-box').data("entityId");
+            var href = $nodeA.attr('href');
+            var tab = (this.detailsView)
                     ? this.detailsView.$el.find(".tab-pane.active").attr("id")
                     : undefined;
             if (href) {
                 if (tab) {
-                    href = href+"/"+tab
-                    stateId = entityId+"/"+tab
-                    this.preselectTab(tab)
+                    href = href+"/"+tab;
+                    stateId = entityId+"/"+tab;
+                    this.preselectTab(tab);
                 }
                 Backbone.history.navigate(href);
-                this.displayEntityId(entityId, nodeSpan.data("app-id"));
+                this.displayEntityId(entityId, $nodeSpan.data("app-id"));
             } else {
-                log("no a.href in clicked target")
-                log(nodeSpan)
+                log("no a.href in clicked target");
+                log($nodeSpan);
             }
         },
-        displayEntityId:function (id, appName, afterLoad) {
+
+        displayEntityId: function (id, appName, afterLoad) {
             var that = this;
-            this.highlightEntity(id)
+            this.highlightEntity(id);
 
             var entityLoadFailed = function() {
                 return that.displayEntityNotFound(id);
@@ -348,17 +345,17 @@ define([
                     return; 
                 }
             }
-            
-            var app = new Application.Model(),
-                entitySummary = new EntitySummary.Model;
 
-            app.url = "/v1/applications/" + appName
+            var app = new Application.Model();
+            var entitySummary = new EntitySummary.Model;
+
+            app.url = "/v1/applications/" + appName;
             entitySummary.url = "/v1/applications/" + appName + "/entities/" + id;
 
             // in case the server response time is low, fade out while it refreshes
             // (since we can't show updated details until we've retrieved app + entity details)
-            ViewUtils.fadeToIndicateInitialLoad($("div#details"))
-            
+            ViewUtils.fadeToIndicateInitialLoad($("div#details"));
+
             $.when(app.fetch(), entitySummary.fetch())
                 .done(function() {
                     that.showDetails(app, entitySummary);
@@ -375,42 +372,44 @@ define([
             var $target = $(event.currentTarget);
             var $treeBox = $target.closest('.tree-box');
             if ($target.hasClass('tr-expand')) {
-                this.showChildrenOf($treeBox, false)
+                this.showChildrenOf($treeBox, false);
             } else if ($target.hasClass('tr-expand-all')) {
-                this.showChildrenOf($treeBox, true)
+                this.showChildrenOf($treeBox, true);
             } else if ($target.hasClass('tr-collapse')) {
-                this.hideChildrenOf($treeBox, false)
+                this.hideChildrenOf($treeBox, false);
             } else if ($target.hasClass('tr-collapse-all')) {
-                this.hideChildrenOf($treeBox, true)
+                this.hideChildrenOf($treeBox, true);
             } else {
                 // default - toggle
                 if ($treeBox.children('.node-children').is(':visible')) {
-                    this.hideChildrenOf($treeBox, false)
+                    this.hideChildrenOf($treeBox, false);
                 } else {
-                    this.showChildrenOf($treeBox, false)
+                    this.showChildrenOf($treeBox, false);
                 }
             }
             // hide the popup menu
             this.cancelHoverTimer();
-            $('.light-popup').hide()
+            $('.light-popup').hide();
             // don't let other events interfere
             return false
         },
+
         hideChildrenOf: function($treeBox, recurse) {
             var that = this;
             if (recurse) {
                 $treeBox.children('.node-children').children().each(function (index, childBox) {
                     that.hideChildrenOf($(childBox), recurse)
-                })
+                });
             }
-            $treeBox.children('.node-children').slideUp(300)
-            $treeBox.children('.entity_tree_node_wrapper').find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right')
+            $treeBox.children('.node-children').slideUp(300);
+            $treeBox.children('.entity_tree_node_wrapper').find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right');
         },
+
         showChildrenOf: function($treeBox, recurse) {
             var that = this;
             var idToExpand = $treeBox.children('.entity_tree_node_wrapper').attr('id');
             var model = this.collection.get(idToExpand);
-            if (model==null) {
+            if (model == null) {
                 // not yet loaded; parallel thread should load
                 return;
             }
@@ -428,43 +427,43 @@ define([
                         if (recurse) {
                             $treeBox.children('.node-children').children().each(function (index, childBox) {
                                 _.defer( function() { that.showChildrenOf($(childBox), recurse) } );
-                            })
-                        }                        
+                            });
+                        }
                     }
                 })
             }
-            $treeBox.children('.node-children').slideDown(300)
-            $treeBox.children('.entity_tree_node_wrapper').find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down')            
+            $treeBox.children('.node-children').slideDown(300);
+            $treeBox.children('.entity_tree_node_wrapper').find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
             if (recurse) {
                 $treeBox.children('.node-children').children().each(function (index, childBox) {
-                    that.showChildrenOf($(childBox), recurse)
+                    that.showChildrenOf($(childBox), recurse);
                 })
             }
         },
-        
+
         /**
          * Causes the tab with the given name to be selected automatically when
          * the view is next rendered.
          */
         preselectTab: function(tab, tabDetails) {
-            this.currentTab = tab
+            this.currentTab = tab;
             this.currentTabDetails = tabDetails;
         },
 
         showDetails: function(app, entitySummary) {
-            var self = this;
-            ViewUtils.cancelFadeOnceLoaded($("div#details"))
-            
-            var whichTab = this.currentTab
+            var that = this;
+            ViewUtils.cancelFadeOnceLoaded($("div#details"));
+
+            var whichTab = this.currentTab;
             if (whichTab === undefined) {
                 whichTab = "summary";
                 if (this.detailsView) {
                     whichTab = this.detailsView.$el.find(".tab-pane.active").attr("id");
-                    this.detailsView.close()
+                    this.detailsView.close();
                 }
             }
             if (this.detailsView) {
-                this.detailsView.close()
+                this.detailsView.close();
             }
             this.detailsView = new EntityDetailsView({
                 model:entitySummary,
@@ -473,47 +472,47 @@ define([
                 preselectTab:whichTab,
                 preselectTabDetails:this.currentTabDetails,
             });
-            
+
             this.detailsView.on("entity.expunged", function() {
-                self.preselectTab("summary");
-                var id = self.selectedEntityId;
-                var model = self.collection.get(id);
+                that.preselectTab("summary");
+                var id = that.selectedEntityId;
+                var model = that.collection.get(id);
                 if (model && model.get("parentId")) {
-                    self.displayEntityId(model.get("parentId"));
-                } else if (self.collection) {
-                    self.displayEntityId(self.collection.first().id);
+                    that.displayEntityId(model.get("parentId"));
+                } else if (that.collection) {
+                    that.displayEntityId(that.collection.first().id);
                 } else if (id) {
-                    self.displayEntityNotFound(id);
+                    that.displayEntityNotFound(id);
                 } else {
-                    self.displayEntityNotFound("?");
+                    that.displayEntityNotFound("?");
                 }
-                self.collection.fetch();
+                that.collection.fetch();
             });
             this.detailsView.render( $("div#details") );
         },
 
-        highlightEntity:function (id) {
-            if (id) this.selectedEntityId = id
-            else id = this.selectedEntityId
-            
-            $(".entity_tree_node_wrapper").removeClass("active")
+        highlightEntity: function(id) {
+            if (id) this.selectedEntityId = id;
+            else id = this.selectedEntityId;
+
+            $(".entity_tree_node_wrapper").removeClass("active");
             if (id) {
                 var $selectedNode = $(".entity_tree_node_wrapper#"+id);
                 // make this node active
-                $selectedNode.addClass("active")
-                
+                $selectedNode.addClass("active");
+
                 // open the parent nodes if needed
                 var $nodeToOpenInParent = $selectedNode;
                 while ($nodeToOpenInParent.length && !$nodeToOpenInParent.is(':visible')) {
                     $nodeToOpenInParent = $nodeToOpenInParent.closest('.node-children').closest('.tree-box');
-                    this.showChildrenOf($nodeToOpenInParent)
+                    this.showChildrenOf($nodeToOpenInParent);
                 }
-                
+
                 // if we want to auto-expand the children of the selected node:
 //              this.showChildrenOf($selectedNode.closest('.tree-box'), false)
             }
         }
-    })
+    });
 
-    return ApplicationTreeView
+    return ApplicationTreeView;
 })

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99c01191/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js b/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
index c102a67..51a7c33 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
@@ -43,7 +43,7 @@ define([
                 this.$("div.catalogItemId").show();
             else
                 this.$("div.catalogItemId").hide();
-            
+
             this.options.tabView.configView = new EntityConfigView({
                 model:this.options.model,
                 tabView:this.options.tabView,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99c01191/usage/jsgui/src/main/webapp/assets/tpl/apps/summary.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/summary.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/summary.html
index 0965068..ed90c75 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/apps/summary.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/summary.html
@@ -73,9 +73,9 @@ under the License.
 
         <div class="additional-info-on-problem hide" style="margin-top: 12px;">
         </div>
- 
-     <div id="status-icon" style="display: inline-block; padding-top: 12px; padding-bottom: 18px; padding-right: 8px; position: absolute; right: 0; top: 0;"></div>
-        
+
+        <div id="status-icon" style="display: inline-block; padding-top: 12px; padding-bottom: 18px; padding-right: 8px; position: absolute; right: 0; top: 0;"></div>
+
    </div>
   </div>
  </div>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/99c01191/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
index 3109230..88e671a 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/tree-item.html
@@ -21,32 +21,33 @@ under the License.
 <%
     var isLoaded = (model ? true : false);
     var isApp = (parentId ? false : true);
-    
+
     if (!isLoaded) {
 %>
         <i>Loading... (<%= id %>)</i>
 <%  } else {
         var hasChildren = model.hasChildren();
         var iconUrl = model.get('iconUrl');
-                 
+
         var entityIconSize = isApp ? 40 : 30;
         var statusIconSize = isApp ? 24 : 16;
-        
+
         var chevronLeft = (isApp ? 5.5 : 1.5);
         var minHeight = hasChildren && statusIconUrl ? entityIconSize : 24;
         var statusColumnWidth = hasChildren || statusIconUrl || (!isApp && !iconUrl /* for children, insert space so things line up */) ? statusIconSize : 0;
 %>
-  
+
   <span class="entity_tree_node name entity" id="span-<%= id %>" 
         data-entity-type="<%= model.get('type') %>" data-parent-id="<%= parentId %>" data-app-id="<%= model.get('applicationId') %>">
     <a href="#v1/applications/<%= model.get('applicationId') %>/entities/<%= id %>">
-    
+
       <div style="min-width: <%= statusColumnWidth + (iconUrl ? entityIconSize : 6)%>px; min-height: <%= minHeight %>px; max-height: 40px; display: inline-block; margin-right: 4px; vertical-align: middle;">
         <% if (statusIconUrl) { %>
         <div style="position: absolute; left: 0px; margin: auto; top: <%= isApp && hasChildren ? 3 : 2 %>px;<% if (!hasChildren) { %> bottom: 0px;<% } %>">
             <img src="<%= statusIconUrl %>" style="max-width: <%= statusIconSize %>px; max-height: <%= statusIconSize %>px; margin: auto; position: absolute; top: -1px;<% if (!hasChildren) { %> bottom: 0px;<% } %>">
         </div>
         <% } %>
+
         <% if (hasChildren) { %>
         <div style="position: absolute; left: <%= chevronLeft %>px; margin: auto; <%= statusIconUrl ? "bottom: -1px;" : isApp ? "top: 6px;" : "top: 6px;" %>">
             <div class="toggler-icon icon-chevron-right tree-node-state tree-change">
@@ -62,13 +63,14 @@ under the License.
             </div>
         </div>
         <% } %>
+
         <% if (iconUrl) { %>
             <img src="<%= iconUrl %>" style="max-width: <%= entityIconSize %>px; max-height: <%= entityIconSize %>px; position: absolute; left: <%= statusColumnWidth %>px; top: 0; bottom: 0; margin: auto;">
         <% } %>
       </div>
      
       <span style="max-height: 18px; padding-right: 6px; position: relative; margin: auto; top: 2px; bottom: 0;"><%= model.get('name') %></span>
-         
+
     </a>
   </span>
 


[5/7] incubator-brooklyn git commit: Fix add-app-wizard for enums

Posted by he...@apache.org.
Fix add-app-wizard for enums

Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/b02bd177
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/b02bd177
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/b02bd177

Branch: refs/heads/master
Commit: b02bd17710d20104e2f4559c17a91769b7f36126
Parents: d2a5d4b
Author: Aled Sage <al...@gmail.com>
Authored: Wed Oct 7 21:04:35 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 7 21:04:35 2015 +0100

----------------------------------------------------------------------
 .../webapp/assets/tpl/app-add-wizard/required-config-entry.html    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b02bd177/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/required-config-entry.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/required-config-entry.html b/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/required-config-entry.html
index 1478963..b802624 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/required-config-entry.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/app-add-wizard/required-config-entry.html
@@ -34,7 +34,7 @@ under the License.
             element = null;
         for (var i = 0; i < length; i++) {
           element = data.possibleValues[i]; %>
-        <option value="<%= element.value %>"><% if (data.defaultValue == element.value) { %> selected="selected"<% } %>><%= element.description %></option>
+        <option value="<%= element.value %>"<% if (data.defaultValue == element.value) { %> selected="selected"<% } %>><%= element.description %></option>
         <% } %>
       </select>
     <% } else { %>