brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [02/50] [abbrv] brooklyn-ui git commit: sensible format drill-down for tasks, including children and other background/submitted tasks (see HasTaskChildren for hints on the difference); also tidy display of some effectors and task details, better display
Date Mon, 01 Feb 2016 17:51:46 GMT
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js b/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
new file mode 100644
index 0000000..50658b5
--- /dev/null
+++ b/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
@@ -0,0 +1,312 @@
+/**
+ * Displays details on an activity/task
+ */
+define([
+    "underscore", "jquery", "backbone", "brooklyn-utils", "view/viewutils", "formatJson",
+    "model/task-summary",
+    "text!tpl/apps/activity-details.html", "text!tpl/apps/activity-table.html", 
+    "text!tpl/apps/activity-row-details.html", "text!tpl/apps/activity-row-details-main.html",
+    "text!tpl/apps/activity-full-details.html", 
+    "bootstrap", "formatJson", "jquery-datatables", "datatables-extensions", "moment"
+], function (_, $, Backbone, Util, ViewUtils, FormatJSON,
+    TaskSummary,
+    ActivityDetailsHtml, ActivityTableHtml, ActivityRowDetailsHtml, ActivityRowDetailsMainHtml,
ActivityFullDetailsHtml) {
+
+    var ActivityDetailsView = Backbone.View.extend({
+        template:_.template(ActivityDetailsHtml),
+        taskLink: '',
+        task: null,
+        /* children of this task; see HasTaskChildren for difference between this and sub(mitted)Tasks
*/
+        childrenTable: null,
+        /* tasks in the current execution context (this.collections) whose submittedByTask
+         * is the task we are drilled down on. this defaults to the passed in collection,

+         * which will be the last-viewed entity's exec-context; when children cross exec-context
+         * boundaries we have to rewire to point to the current entity's exec-context / tasks
*/
+        subtasksTable: null,
+        children: null,
+        breadcrumbs: [],
+        events:{
+            "click #activities-children-table .activity-table tr":"childrenRowClick",
+            "click #activities-submitted-table .activity-table tr":"submittedRowClick",
+            'click .showDrillDownSubmittedByAnchor':'showDrillDownSubmittedByAnchor',
+            'click .backDrillDown':'backDrillDown'
+        },
+        // requires taskLink or task; breadcrumbs is optional
+        initialize:function () {
+            this.taskLink = this.options.taskLink
+            if (this.options.task) {
+                this.task = this.options.task
+                if (!this.taskLink) this.taskLink = this.task.get('links').self
+            }
+            if (this.options.breadcrumbs) this.breadcrumbs = this.options.breadcrumbs
+
+            this.$el.html(this.template({ taskLink: this.taskLink, task: this.task, breadcrumbs:
this.breadcrumbs }))
+            this.$el.addClass('activity-detail-panel')
+                        
+            this.$('#activities-children-table').html(_.template(ActivityTableHtml))
+            var that = this,
+                $childrenTable = this.$('#activities-children-table .activity-table');
+            $childrenTable.attr('width', 569-6-6 /* subtract padding */)
+            this.childrenTable = ViewUtils.myDataTable($childrenTable, {
+                "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull
) {
+                    $(nRow).attr('id', aData[0])
+                    $(nRow).addClass('activity-row')
+                },
+                "aoColumnDefs": [ {
+                        "mRender": function ( data, type, row ) { return Util.prep(data)
},
+                        "aTargets": [ 1, 2, 3 ]
+                     }, { 
+                        "bVisible": false, 
+                        "aTargets": [ 0 ] 
+                     } ]            
+            });
+
+            this.$('#activities-submitted-table').html(_.template(ActivityTableHtml))
+            var that = this,
+                $subtasksTable = this.$('#activities-submitted-table .activity-table');
+            $subtasksTable.attr('width', 569-6-6 /* subtract padding */)
+            this.subtasksTable = ViewUtils.myDataTable($subtasksTable, {
+                "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull
) {
+                    $(nRow).attr('id', aData[0])
+                    $(nRow).addClass('activity-row')
+                },
+                "aoColumnDefs": [ {
+                        "mRender": function ( data, type, row ) { return Util.prep(data)
},
+                        "aTargets": [ 1, 2, 3 ]
+                     }, { 
+                        "bVisible": false, 
+                        "aTargets": [ 0 ] 
+                     } ]            
+            });
+        
+            ViewUtils.attachToggler(this.$el)
+        
+            if (this.task) {
+                this.renderTask(true)
+            } else {                
+                this.$el.css('cursor', 'wait')
+                this.refreshNow(true)
+            }
+            this.renderSubtasks()
+        
+            this.callPeriodically("refreshNow", function () {
+                that.refreshNow()
+            }, 1000);
+
+            if (this.collection) {
+                this.collection.on("reset", this.renderSubtasks, this);
+                // could lean on parent's poll for the task itself, but instead we poll (and
more often)
+//                this.collection.on("reset", this.renderTask, this);
+            }
+        },
+        
+        refreshNow: function(initial) {
+            var that = this
+            $.get(this.taskLink, function(data) {
+                that.task = new TaskSummary.Model(data)
+                that.renderTask(initial)
+            })
+        },
+        renderTask: function(initial) {
+            // update task fields
+            var that = this
+            
+            this.updateField('displayName')
+            this.updateField('entityDisplayName')
+            this.updateField('id')
+            this.updateField('description')
+            this.updateField('currentStatus')
+            this.updateField('tags')
+            
+            var submitTimeUtc = this.updateFieldWith('submitTimeUtc',
+                function(v) { return moment(v).format('D MMM YYYY H:mm:ss.SSS')+"  
<i>"+moment(v).fromNow()+"</i>" })
+            var startTimeUtc = this.updateFieldWith('startTimeUtc',
+                function(v) { return moment(v).format('D MMM YYYY H:mm:ss.SSS')+" &nbsp;
<i>"+moment(v).fromNow()+"</i>" })
+            this.updateFieldWith('endTimeUtc',
+                function(v) { return moment(v).format('D MMM YYYY H:mm:ss.SSS')+" &nbsp;
<i>"+moment(v).from(startTimeUtc, true)+" later</i>" })
+
+            ViewUtils.updateTextareaWithData($(".task-json .for-textarea", this.$el), 
+                FormatJSON(this.task.toJSON()), false, 150, 400)
+
+            ViewUtils.updateTextareaWithData($(".task-detail .for-textarea", this.$el), 
+                this.task.get('detailedStatus'), false, 30, 100)
+
+            this.updateFieldWith('submittedByTask',
+                function(v) { return "<a class='showDrillDownSubmittedByAnchor handy'
link='"+_.escape(v.link)+"'>"+
+                    that.displayTextForLinkedTask(v)+"</a>" })
+
+            if (this.task.get("children").length==0)
+                $('.toggler-region.tasks-children', this.$el).hide();
+            
+            if (initial) {
+                // on first load, clear any funny cursor
+                this.$el.css('cursor', 'auto')
+                
+                // and set up to load children (now that the task is guaranteed to be loaded)
+                this.children = new TaskSummary.Collection()
+                this.children.url = this.task.get("links").children
+                this.children.on("reset", this.renderChildren, this)
+                this.callPeriodically("refreshChildren", function () {
+                    that.children.fetch({reset: true});
+                }, 3000);
+                that.children.fetch({reset: true});
+                
+                $.get(this.task.get("links").entity, function(entity) {
+                    if (that.collection==null || entity.links.activities != that.collection.url)
{
+                        // need to rewire collection to point to the right ExecutionContext
+                        that.collection = new TaskSummary.Collection()
+                        that.collection.url = entity.links.activities
+                        that.collection.on("reset", this.renderSubtasks, this)
+                        that.callPeriodically("refreshSubmittedTasks", function () {
+                            that.collection.fetch({reset: true});
+                        }, 3000);
+                        that.collection.fetch({reset: true});
+                    }
+                });
+            }
+        },
+        renderChildren: function() {
+            var that = this
+            var children = this.children
+            ViewUtils.updateMyDataTable(this.childrenTable, children, function(task, index)
{
+                return [ task.get("id"),
+                         (task.get("entityId")!=that.task.get("entityId") ? task.get("entityDisplayName")
+ ": " : "") + 
+                         task.get("displayName"),
+                         moment(task.get("submitTimeUtc")).calendar(),
+                         task.get("currentStatus")
+                    ]; 
+                });
+            if (children && children.length>0) {
+                $('.toggler-region.tasks-children', this.$el).show();
+            } else {
+                $('.toggler-region.tasks-children', this.$el).hide();
+            }
+        },
+        renderSubtasks: function() {
+            var that = this
+            if (!this.collection) {
+                $('.toggler-region.tasks-submitted', this.$el).hide();
+                return;
+            }
+            // find tasks submitted by this one which aren't included as children
+            // this uses collections -- which is everything in the current execution context
+            var subtasks = []
+            for (taskI in this.collection.models) {
+                var task = this.collection.models[taskI]
+                var submittedBy = task.get("submittedByTask")
+                if (submittedBy!=null && submittedBy.metadata.id == this.task.id
&&
+                        this.children.get(task.id)==null) {
+                    subtasks.push(task)
+                }
+            }
+            ViewUtils.updateMyDataTable(this.subtasksTable, subtasks, function(task, index)
{
+                return [ task.get("id"),
+                         (task.get("entityId")!=that.task.get("entityId") ? task.get("entityDisplayName")
+ ": " : "") + 
+                         task.get("displayName"),
+                         moment(task.get("submitTimeUtc")).calendar(),
+                         task.get("currentStatus")
+                    ]; 
+                });
+            if (subtasks && subtasks.length>0) {
+                $('.toggler-region.tasks-submitted', this.$el).show();
+            } else {
+                $('.toggler-region.tasks-submitted', this.$el).hide();
+            }
+        },
+        
+        displayTextForLinkedTask: function(v) {
+            return v.metadata.taskName ? 
+                    (v.metadata.entityDisplayName ? _.escape(v.metadata.entityDisplayName)+"
<b>"+_.escape(v.metadata.taskName)+"</b>" : 
+                        _.escape(v.metadata.taskName)) :
+                    v.metadata.taskId ? _.escape(v.metadata.taskId) : 
+                    _.escape(v.link)
+        },
+        updateField: function(field) {
+            return this.updateFieldWith(field, function(v) { return _.escape(v) })
+        },
+        updateFieldWith: function(field, f) {
+            var v = this.task.get(field)
+            if (v) {
+                $('.updateField-'+field, this.$el).html( f(v) );
+                $('.ifField-'+field, this.$el).show();
+                return v
+            } else {
+                $('.ifField-'+field, this.$el).hide();
+            }
+        },
+        childrenRowClick:function(evt) {
+            var that = this;
+            var row = $(evt.currentTarget).closest("tr");
+            var table = $(evt.currentTarget).closest(".activity-table");
+            var id = row.attr("id");
+            this.showDrillDownTask("subtask of", this.children.get(id).get("links").self,
this.children.get(id))
+        },
+        submittedRowClick:function(evt) {
+            var that = this;
+            var row = $(evt.currentTarget).closest("tr");
+            var table = $(evt.currentTarget).closest(".activity-table");
+            var id = row.attr("id");
+            // submitted tasks are guaranteed to be in the collection, so this is safe
+            this.showDrillDownTask("subtask of", this.collection.get(id).get('links').self)
+        },
+        
+        showDrillDownSubmittedByAnchor: function(from) {
+            var link = $(from.target).closest('a').attr("link")
+            this.showDrillDownTask("submitter of", link)
+        },
+        showDrillDownTask: function(relation, newTaskLink, newTask) {
+            log("activities deeper drill down - "+newTaskLink)
+            var $t = this.$el.closest('.slide-panel')
+            $t2 = $t.after('<div>').next()
+            $t2.addClass('slide-panel')
+            
+            newBreadcrumbs = [ relation + ' ' +
+                this.task.get('entityDisplayName') + ' ' +
+                this.task.get('displayName') ].concat(this.breadcrumbs)
+            var activityDetailsPanel = new ActivityDetailsView({
+                taskLink: newTaskLink,
+                task: newTask,
+                collection: this.collection,
+                breadcrumbs: newBreadcrumbs
+            })
+
+            // load drill-down page
+            $t2.html(activityDetailsPanel.render().el)
+
+            $t.animate({
+                    left: -600
+                }, 300, function() { 
+                    $t.hide() 
+                });
+
+            $t2.show().css({
+                    left: 600
+                    , top: 0
+                }).animate({
+                    left: 0
+                }, 300);
+        },
+        backDrillDown: function(event) {
+            log("activities drill back from "+this.taskLink)
+            var that = this
+            var $t2 = this.$el.closest('.slide-panel')
+            var $t = $t2.prev()
+            
+            $t2.animate({
+                    left: 569 //prevTable.width()
+                }, 300, function() {
+                    that.$el.html('')
+                    $t2.remove()
+                    that.remove()
+                });
+
+            $t.show().css({
+                    left: -600 //-($t2.width())
+                }).animate({
+                    left: 0
+                }, 300);
+        }
+    });
+
+    return ActivityDetailsView;
+});

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js b/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
index 30d6265..131317b 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
@@ -3,11 +3,12 @@
  */
 define([
     "underscore", "jquery", "backbone", "brooklyn-utils", "view/viewutils",
+    "view/activity-details",
     "text!tpl/apps/activities.html", "text!tpl/apps/activity-table.html", 
     "text!tpl/apps/activity-row-details.html", "text!tpl/apps/activity-row-details-main.html",
     "text!tpl/apps/activity-full-details.html", 
-    "bootstrap", "formatJson", "jquery-datatables", "datatables-extensions"
-], function (_, $, Backbone, Util, ViewUtils, 
+    "bootstrap", "formatJson", "jquery-datatables", "datatables-extensions", "moment"
+], function (_, $, Backbone, Util, ViewUtils, ActivityDetailsView, 
     ActivitiesHtml, ActivityTableHtml, ActivityRowDetailsHtml, ActivityRowDetailsMainHtml,
ActivityFullDetailsHtml) {
 
     var ActivitiesView = Backbone.View.extend({
@@ -16,12 +17,12 @@ define([
         refreshActive:true,
         selectedId:null,
         selectedRow:null,
+        activityDetailsPanel:null,
         events:{
             "click .activity-table tr":"rowClick",
             'click .refresh':'refreshNow',
             'click .toggleAutoRefresh':'toggleAutoRefresh',
             'click .showDrillDown':'showDrillDown',
-            'click .backDrillDown':'backDrillDown',
             'click .toggleFullDetail':'toggleFullDetail'
         },
         initialize:function () {
@@ -80,13 +81,23 @@ define([
         },
         updateActivitiesNow: function() {
             var that = this;
-            if (that.table == null || this.collection.length==0) {
+            if (this.table == null || this.collection.length==0) {
                 // nothing to do
             } else {
-                ViewUtils.updateMyDataTable(that.table, that.collection, function(task, index)
{
+                var topLevelTasks = []
+                for (taskI in this.collection.models) {
+                    var task = this.collection.models[taskI]
+                    var submitter = task.get("submittedByTask")
+                    if ((submitter==null) ||
+                        (submitter!=null && this.collection.get(submitter.metadata.id)==null)
+                    ) {                        
+                        topLevelTasks.push(task)
+                    }
+                }
+                ViewUtils.updateMyDataTable(that.table, topLevelTasks, function(task, index)
{
                     return [ task.get("id"),
                              task.get("displayName"),
-                             task.get("submitTimeUtc"),
+                             moment(task.get("submitTimeUtc")).calendar(),
                              task.get("currentStatus")
                     ]; 
                 });
@@ -121,7 +132,6 @@ define([
                 this.selectedRow = null;
                 this.selectedId = null;
                 this.hideFullActivity(id);
-//                this.showDrillDown(null);
             } else {
                 row.addClass("selected");
                 this.selectedRow = row[0];
@@ -130,8 +140,14 @@ define([
                 this.showDetailRow(false);
             }
         },
-        
         showDetailRow: function(updateOnly) {
+//            // auto-drill-down -- useful for testing
+//            if (this.selectedId==null) {
+//                log("auto-selecting")
+//                this.selectedId = this.collection.models[0].get('id')
+//                this.showDrillDownTask(this.selectedId)
+//            }
+            
             var id = this.selectedId,
                 that = this;
             if (id==null) return;
@@ -163,71 +179,6 @@ define([
                 $('tr#'+id).next().find('.row-expansion .opened-row-details').slideDown(300)
             }
         },
-        backDrillDown: function(parent) {
-            var $t = this.$('#activities-root')
-            var $t2 = this.$('#1234') 
-            $t2.animate({
-                    left: 569 //prevTable.width()
-                }, 500, function() { 
-                    $t2.remove() 
-                });
-
-            $t.show().css({
-                    left: -600 //-($t2.width())
-                }).animate({
-                    left: 0
-                }, 500);
-        },
-        showDrillDown: function(event) {
-            var parentId = $(event.currentTarget).closest("td.row-expansion").attr("id");
-            log("WIP drill down - "+parentId)
-            log(this.collection)
-            notImplementedYet;
-            
-            var $t = this.$('#activities-root')
-            //   style="display: inline-block; overflow: hidden; white-space: nowrap;"
-            $t2 = $t.after('<div>').next()
-            $t2.attr('id', '1234')
-            $t2.addClass('slide-panel')
-            $t2.hide()
-            $t2.append('<div class="subpanel-header-row backDrillDown">'+
-                    '<i class="backDrillDown icon-chevron-left handy" rel="tooltip" title="Return
to sibling tasks" style="margin-top: 4px;"></i>'+
-                    '&nbsp; Sub-tasks of: \'sample 1234\''+
-                    '</div>')
-            $t2.append('<div class="table-scroll-wrapper"></div>')
-            $t2.find('.table-scroll-wrapper').append(_.template(ActivityTableHtml))
-            $t2t = $t2.find('table.activity-table')
-            
-            table2 = ViewUtils.myDataTable( $t2t, {
-                "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull
) {
-                    $(nRow).attr('id', aData[0])
-                    $(nRow).addClass('activity-row')
-                },
-                "aoColumnDefs": [
-                                 {
-                                     "mRender": function ( data, type, row ) {
-                                         return Util.prep(data)
-                                     },
-                                     "aTargets": [ 1, 2, 3 ]
-                                 },
-                                 { "bVisible": false,  "aTargets": [ 0 ] }
-                             ]            
-            });
-            table2.fnAddData( [ "XXX", "Sample sub-task", "MOCK", "(work in progress)" ])
-
-            $t.animate({
-                    left: -600
-                }, 300, function() { 
-                    $t.hide() 
-                });
-
-            $t2.show().css({
-                    left: 600
-                    , top: 0
-                }).animate({
-                    left: 0
-                }, 300);
-        },
         toggleFullDetail: function(evt) {
             var i = $('.toggleFullDetail');
             var id = i.closest("td.row-expansion").attr('id')
@@ -238,18 +189,46 @@ define([
                 this.hideFullActivity(id)
         },
         showFullActivity: function(id) {
-            log("FULL for "+id)
             id = this.selectedId
             var $details = $("td.row-expansion#"+id+" .expansion-footer");
             var task = this.collection.get(id);
             var html = _.template(ActivityFullDetailsHtml, { task: task });
             $details.html(html);
             $details.slideDown(100);
+            _.defer(function() { ViewUtils.setHeightAutomatically($('textarea',$details),
30, 200) })
         },
         hideFullActivity: function(id) {
             id = this.selectedId
             var $details = $("td.row-expansion#"+id+" .expansion-footer");
             $details.slideUp(100);
+        },
+        showDrillDown: function(event) {
+            this.showDrillDownTask($(event.currentTarget).closest("td.row-expansion").attr("id"));
+        },
+        showDrillDownTask: function(taskId) {    
+            this.activityDetailsPanel = new ActivityDetailsView({
+                task: this.collection.get(taskId),
+                collection: this.collection,
+                breadcrumbs: ''
+            })
+            var $t = this.$('#activities-root')
+            $t2 = $t.after('<div>').next()
+            $t2.addClass('slide-panel')
+            
+            $t2.html(this.activityDetailsPanel.render().el)
+
+            $t.animate({
+                    left: -600
+                }, 300, function() { 
+                    $t.hide() 
+                });
+
+            $t2.show().css({
+                    left: 600
+                    , top: 0
+                }).animate({
+                    left: 0
+                }, 300);
         }
     });
 

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js b/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
index 4411b93..8dc0d96 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
@@ -131,7 +131,9 @@ define([
             $togglers.click(this.onTogglerClick);
         },
         onTogglerClick: function(event) {
-            var root = $(event.currentTarget).closest(".toggler-header");
+            ViewUtils.onTogglerClickElement($(event.currentTarget).closest(".toggler-header"));
+        },
+        onTogglerClickElement: function(root) {
             root.toggleClass("user-hidden");
             $(".toggler-icon", root).toggleClass("icon-chevron-left").toggleClass("icon-chevron-down");
             var next = root.next();
@@ -151,15 +153,23 @@ define([
                 $ta.val("");
             }
             if (show) {
-                $div.show(100);
-                $ta.css("height", minPx);
-                // scrollHeight prop works sometimes (e.g. groovy page) but not others (e.g.
summary)
-                var height = $ta.prop("scrollHeight");
+                ViewUtils.setHeightAutomatically($ta, minPx, maxPx, false)
+                if (alwaysShow) { $div.show(100); }
+            } else {
+                $div.hide();
+            }
+        },
+        setHeightAutomatically: function($ta, minPx, maxPx, deferred) {
+            var height = $ta.prop("scrollHeight");
+            if ($ta.css("padding-top")) height -= parseInt($ta.css("padding-top"), 10)
+            if ($ta.css("padding-bottom")) height -= parseInt($ta.css("padding-bottom"),
10)
+//            log("scroll height "+height+" - old real height "+$ta.css("height"))
+            if (height==0 && !deferred) {
+                _.defer(function() { ViewUtils.setHeightAutomatically($ta, minPx, maxPx,
true) })
+            } else {
                 height = Math.min(height, maxPx);
                 height = Math.max(height, minPx);
                 $ta.css("height", height);
-            } else {
-                $div.hide();
             }
         },
         each: function(collection, fn) {

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-details.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-details.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-details.html
new file mode 100644
index 0000000..81eafce
--- /dev/null
+++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-details.html
@@ -0,0 +1,106 @@
+<div class="subpanel-header-row">
+    <div>
+     <div style="float: left;"><i class="backDrillDown icon-chevron-left handy"
rel="tooltip" title="Back up one level" style="margin-top: 3px;"></i>&nbsp;</div>
+     <div style="margin-bottom: 6px;">
+      <span style="font-weight: 400;" class="ifField-entityDisplayName"><span class="updateField-entityDisplayName"/>:</span>
+      <div style="display: inline-block;" class="updateField-displayName">Loading...</div>
+     </div>
+     <% for (crumb in breadcrumbs) { %>
+     <div style="margin-right: 20px; font-weight: 400; font-size: 80%; text-align: right;
line-height: 14px;">
+        <%= breadcrumbs[crumb] %></span>
+     </div>
+     <% } %>
+    </div>
+</div>
+
+<div class="activity-details-section activity-description">
+    <span class="updateField-description"/>
+</div>
+<div class="activity-details-section activity-status">
+    <span class="updateField-currentStatus"/>
+</div>
+
+  <div class="toggler-region task-detail">
+    <div class="toggler-header">
+      <div class="toggler-icon icon-chevron-down"></div>
+      <div><b>Summary</b></div>
+    </div>
+    <div>
+
+<div class="activity-details-section activity-name-and-id">
+    <span class="activity-label">Name:</span> <span class="updateField-displayName">Loading...</span>
<br/>
+    <span class="activity-label">ID:</span> <span class="updateField-id"><%=
taskLink %></span>
+</div>
+<div class="activity-details-section activity-time">
+    <!-- these are dynamic -->
+    <div class="ifField-submitTimeUtc"><span class="activity-label">Submitted:</span>

+        <span class="updateField-submitTimeUtc"/></div>
+    <div class="ifField-startTimeUtc"><span class="activity-label">Started:</span>
+        <span class="updateField-startTimeUtc"/></div>
+    <div class="ifField-endTimeUtc"><span class="activity-label">Finished:</span>
+        <span class="updateField-endTimeUtc"/></div>
+</div>
+<div class="ifField-tags">
+  <div class="activity-details-section activity-tags">
+    <span class="activity-label">Tags:</span> 
+        <span class="updateField-tags"/>
+  </div>
+</div>
+<div class="ifField-submittedByTask">
+  <div class="activity-details-section activity-tags">
+    <span class="activity-label">Submitted by:</span> 
+        <span class="updateField-submittedByTask"/>
+  </div>
+</div>
+
+    </div>
+  </div>
+    
+
+  <div class="toggler-region task-detail">
+    <div class="toggler-header">
+      <div class="toggler-icon icon-chevron-down"></div>
+      <div><b>Detailed Status</b></div>
+    </div>
+    <div class="activity-details-section activity-detailStatus">
+     <div class="for-textarea">
+      <textarea id="detailStatus-textrea" readonly="readonly" style="width: 100%;"></textarea>
+     </div>
+    </div>
+  </div>
+    
+  <div class="toggler-region tasks-children">
+    <div class="toggler-header">
+      <div class="toggler-icon icon-chevron-down"></div>
+      <div><b>Children Tasks</b></div>
+    </div>
+    <div class="activity-details-section activity-tasks-children">
+      <div id="activities-children-table" class="table-scroll-wrapper">
+      </div>
+    </div>
+  </div>
+    
+  <div class="toggler-region tasks-submitted">
+    <div class="toggler-header">
+      <div class="toggler-icon icon-chevron-down"></div>
+      <div><b>Background Tasks</b></div>
+    </div>
+    <div class="activity-details-section activity-tasks-submitted">
+      <div id="activities-submitted-table" class="table-scroll-wrapper">
+      </div>
+    </div>
+  </div>
+        
+  <div class="toggler-region task-json">
+    <div class="toggler-header user-hidden">
+      <div class="toggler-icon icon-chevron-left"></div>
+      <div><b>JSON</b></div>
+    </div>
+    <div class="activity-details-section activity-json hide">
+     <div class="for-textarea">
+      <textarea id="json-textrea" readonly="readonly" style="width: 100%;"></textarea>
+     </div>
+    </div>     
+  </div>
+    
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-full-details.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-full-details.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-full-details.html
index cc25b6f..8bec157 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-full-details.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-full-details.html
@@ -1,6 +1,5 @@
 <div class="for-activity-textarea">
-    <textarea readonly="readonly" rows="8" style="width:100%;"><%= 
-    //FormatJSON(task.toJSON())
-    task.attributes.detailedStatus 
+    <textarea readonly="readonly" rows="1" style="width:100%;"><%= 
+        task.attributes.detailedStatus 
     %></textarea>
 </div>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row-details-main.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row-details-main.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row-details-main.html
index af03f66..e6c55cc 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row-details-main.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row-details-main.html
@@ -1,12 +1,8 @@
-    <% if (task.startTimeUtc) { %>started <%= task.startTimeUtc %><% 
-        if (task.submitTimeUtc != task.startTimeUtc) { %> (submitted <%= task.submitTimeUtc
%>)<% } 
-        if (task.endTimeUtc) { %>, finished <%= task.endTimeUtc %> 
+    <% if (task.startTimeUtc) { %>started <%= moment(task.startTimeUtc).fromNow()
%><% 
+        if (task.submitTimeUtc != task.startTimeUtc) { %> (submitted <%= moment(task.submitTimeUtc).from(task.startTimeUtc,
true) %> earlier)<% } 
+        if (task.endTimeUtc) { %>, finished after <%= moment(task.endTimeUtc).from(task.startTimeUtc,
true) %> 
         <% } else { %>, in progress
         <% } %>
     <% } else { %>
-       submitted <%= task.submitTimeUtc %>, waiting
-    <% } %>
-    <% if (task.tags) { %>
-        <br/>
-        tags: <%= task.tags %>
+       submitted <%= moment(task.submitTimeUtc).fromNow() %>, waiting
     <% } %>

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/987f5d03/usage/jsgui/src/test/javascript/config.txt
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/test/javascript/config.txt b/usage/jsgui/src/test/javascript/config.txt
index 8488a6e..3e49c21 100644
--- a/usage/jsgui/src/test/javascript/config.txt
+++ b/usage/jsgui/src/test/javascript/config.txt
@@ -13,6 +13,7 @@
         "jquery-slideto":"js/libs/jquery.slideto.min",
         "jquery-wiggle":"js/libs/jquery.wiggle.min",
         "jquery-ba-bbq":"js/libs/jquery.ba-bbq.min",
+        "moment":"js/libs/moment.min",
         "handlebars":"js/libs/handlebars-1.0.rc.1",
         "brooklyn":"js/libs/brooklyn",
         "brooklyn-utils":"js/libs/brooklyn-utils",


Mime
View raw message