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 2016/02/01 18:52:20 UTC
[02/51] [abbrv] [partial] brooklyn-ui git commit: move subdir from
incubator up a level as it is promoted to its own repo (first non-incubator
commit!)
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/change-name-invoke.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/change-name-invoke.js b/src/main/webapp/assets/js/view/change-name-invoke.js
new file mode 100644
index 0000000..30c2277
--- /dev/null
+++ b/src/main/webapp/assets/js/view/change-name-invoke.js
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Render entity expungement as a modal
+ */
+define([
+ "underscore", "jquery", "backbone", "brooklyn-utils",
+ "text!tpl/apps/change-name-modal.html"
+], function(_, $, Backbone, Util, ChangeNameModalHtml) {
+ return Backbone.View.extend({
+ template: _.template(ChangeNameModalHtml),
+ initialize: function() {
+ this.title = "Change Name of "+this.options.entity.get('name');
+ },
+ render: function() {
+ this.$el.html(this.template({ name: this.options.entity.get('name') }));
+ return this;
+ },
+ onSubmit: function() {
+ var self = this;
+ var newName = this.$("#new-name").val();
+ var url = this.options.entity.get('links').rename + "?name=" + encodeURIComponent(newName);
+ var ajax = $.ajax({
+ type: "POST",
+ url: url,
+ contentType: "application/json",
+ success: function() {
+ self.options.target.reload();
+ },
+ error: function(response) {
+ self.showError(Util.extractError(response, "Error contacting server", url));
+ }
+ });
+ return ajax;
+ },
+ showError: function (message) {
+ this.$(".change-name-error-container").removeClass("hide");
+ this.$(".change-name-error-message").html(message);
+ }
+ });
+});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/effector-invoke.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/effector-invoke.js b/src/main/webapp/assets/js/view/effector-invoke.js
new file mode 100644
index 0000000..7c9e0bd
--- /dev/null
+++ b/src/main/webapp/assets/js/view/effector-invoke.js
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Render an entity effector as a modal.
+ */
+define([
+ "underscore", "jquery", "backbone",
+ "model/location",
+ "text!tpl/apps/effector-modal.html",
+ "text!tpl/app-add-wizard/deploy-location-row.html",
+ "text!tpl/app-add-wizard/deploy-location-option.html",
+ "text!tpl/apps/param.html",
+ "text!tpl/apps/param-list.html",
+ "bootstrap"
+], function (_, $, Backbone, Location, EffectorModalHtml,
+ DeployLocationRowHtml, DeployLocationOptionHtml, ParamHtml, ParamListHtml) {
+
+ var EffectorInvokeView = Backbone.View.extend({
+ template:_.template(EffectorModalHtml),
+ locationRowTemplate:_.template(DeployLocationRowHtml),
+ locationOptionTemplate:_.template(DeployLocationOptionHtml),
+ effectorParam:_.template(ParamHtml),
+ effectorParamList:_.template(ParamListHtml),
+
+ events:{
+ "click .invoke-effector":"invokeEffector",
+ "shown": "onShow",
+ "hide": "onHide"
+ },
+
+ initialize:function () {
+ this.locations = this.options.locations /* for testing */
+ || new Location.Collection();
+ },
+
+ onShow: function() {
+ this.delegateEvents();
+ this.$el.fadeTo(500,1);
+ },
+
+ onHide: function() {
+ this.undelegateEvents();
+ },
+
+ render:function () {
+ var that = this, params = this.model.get("parameters")
+ this.$el.html(this.template({
+ name:this.model.get("name"),
+ entityName:this.options.entity.get("name"),
+ description:this.model.get("description")?this.model.get("description"):""
+ }))
+ // do we have parameters to render?
+ if (params.length !== 0) {
+ this.$(".modal-body").html(this.effectorParamList({}))
+ // select the body of the table we just rendered and append params
+ var $tbody = this.$("tbody")
+ _(params).each(function (param) {
+ // TODO: this should be another view whose implementation is specific to
+ // the type of the parameter (i.e. text, dates, checkboxes etc. can all
+ // be handled separately).
+ $tbody.append(that.effectorParam({
+ name:param.name,
+ type:param.type,
+ description:param.description?param.description:"",
+ defaultValue:param.defaultValue
+ }))
+ })
+ var container = this.$("#selector-container")
+ if (container.length) {
+ this.locations.fetch({async:false})
+ container.empty()
+ var chosenLocation = this.locations[0];
+ container.append(that.locationRowTemplate({
+ initialValue : chosenLocation,
+ rowId : 0
+ }))
+ var $selectLocations = container.find('.select-location')
+ .append(this.locationOptionTemplate({
+ id: "",
+ name: "None"
+ }))
+ .append("<option disabled>------</option>");
+ this.locations.each(function(aLocation) {
+ var $option = that.locationOptionTemplate({
+ id:aLocation.id,
+ url:aLocation.getLinkByName("self"),
+ name:aLocation.getPrettyName()
+ })
+ $selectLocations.append($option)
+ })
+ $selectLocations.each(function(i) {
+ var url = $($selectLocations[i]).parent().attr('initialValue');
+ $($selectLocations[i]).val(url)
+ })
+ }
+ }
+ this.$(".modal-body").find('*[rel="tooltip"]').tooltip()
+ return this
+ },
+
+ extractParamsFromTable:function () {
+ var parameters = {};
+
+ // iterate over the rows
+ // TODO: this should be generic alongside the rendering of parameters.
+ this.$(".effector-param").each(function (index) {
+ var key = $(this).find(".param-name").text();
+ var valElement = $(this).find(".param-value");
+ var value;
+ if (valElement.attr('id') == 'selector-container') {
+ value = $(this).find(".param-value option:selected").attr("value")
+ } else if (valElement.is(":checkbox")) {
+ value = ("checked" == valElement.attr("checked")) ? "true" : "false";
+ } else {
+ value = valElement.val();
+ }
+ //treat empty field as null value
+ if (value !== '') {
+ parameters[key] = value;
+ }
+ });
+ return parameters
+ },
+
+ invokeEffector:function () {
+ var that = this
+ var url = this.model.getLinkByName("self")
+ var parameters = this.extractParamsFromTable()
+ this.$el.fadeTo(500,0.5);
+ $.ajax({
+ type:"POST",
+ url:url+"?timeout=0",
+ data:JSON.stringify(parameters),
+ contentType:"application/json",
+ success:function (data) {
+ that.$el.modal("hide")
+ that.$el.fadeTo(500,1);
+ if (that.options.openTask)
+ that.options.tabView.openTab('activities/subtask/'+data.id);
+ },
+ error: function(data) {
+ that.$el.fadeTo(100,1).delay(200).fadeTo(200,0.2).delay(200).fadeTo(200,1);
+ // TODO render the error better than poor-man's flashing
+ // (would just be connection error -- with timeout=0 we get a task even for invalid input)
+
+ console.error("ERROR invoking effector")
+ console.debug(data)
+ }})
+ // un-delegate events
+ this.undelegateEvents()
+ }
+
+ })
+ return EffectorInvokeView
+})
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/entity-activities.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/entity-activities.js b/src/main/webapp/assets/js/view/entity-activities.js
new file mode 100644
index 0000000..07dc948
--- /dev/null
+++ b/src/main/webapp/assets/js/view/entity-activities.js
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Displays the list of activities/tasks the entity performed.
+ */
+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", "jquery-datatables", "datatables-extensions", "moment"
+], function (_, $, Backbone, Util, ViewUtils, ActivityDetailsView,
+ ActivitiesHtml, ActivityTableHtml, ActivityRowDetailsHtml, ActivityRowDetailsMainHtml, ActivityFullDetailsHtml) {
+
+ var ActivitiesView = Backbone.View.extend({
+ template:_.template(ActivitiesHtml),
+ table:null,
+ refreshActive:true,
+ selectedId:null,
+ selectedRow:null,
+ events:{
+ "click #activities-root .activity-table tr":"rowClick",
+ 'click #activities-root .refresh':'refreshNow',
+ 'click #activities-root .toggleAutoRefresh':'toggleAutoRefresh',
+ 'click #activities-root .showDrillDown':'showDrillDown',
+ 'click #activities-root .toggleFullDetail':'toggleFullDetail'
+ },
+ initialize:function () {
+ _.bindAll(this)
+ this.$el.html(this.template({ }));
+ this.$('#activities-root').html(_.template(ActivityTableHtml))
+ var that = this,
+ $table = that.$('#activities-root .activity-table');
+ that.collection.url = that.model.getLinkByName("activities");
+ that.table = ViewUtils.myDataTable($table, {
+ "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
+ $(nRow).attr('id', aData[0])
+ $(nRow).addClass('activity-row')
+ },
+ "aaSorting": [[ 2, "desc" ]],
+ "aoColumnDefs": [
+ {
+ "mRender": function ( data, type, row ) {
+ return Util.escape(data)
+ },
+ "aTargets": [ 1, 3 ]
+ },
+ {
+ "mRender": function ( data, type, row ) {
+ if ( type === 'display' ) {
+ data = moment(data).calendar();
+ }
+ return Util.escape(data)
+ },
+ "aTargets": [ 2 ]
+ },
+ { "bVisible": false, "aTargets": [ 0 ] }
+ ]
+ });
+
+ // TODO domain-specific filters
+ ViewUtils.addAutoRefreshButton(that.table);
+ ViewUtils.addRefreshButton(that.table);
+
+ ViewUtils.fadeToIndicateInitialLoad($table);
+ that.collection.on("reset", that.renderOnLoad, that);
+ ViewUtils.fetchRepeatedlyWithDelay(this, this.collection,
+ { fetchOptions: { reset: true }, doitnow: true,
+ enablement: function() { return that.refreshActive } });
+ },
+ refreshNow: function() {
+ this.collection.fetch({reset: true});
+ },
+ render:function () {
+ this.updateActivitiesNow();
+ var details = this.options.tabView ? this.options.tabView.options.preselectTabDetails : null;
+ if (details && details!=this.lastPreselectTabDetails) {
+ this.lastPreselectTabDetails = details;
+ // should be a path
+ this.queuedTasksToOpen = details.split("/");
+ }
+ this.tryOpenQueuedTasks();
+ return this;
+ },
+ tryOpenQueuedTasks: function() {
+ if (!this.queuedTasksToOpen || this.tryingOpenQueuedTasks) return;
+ this.openingQueuedTasks = true;
+ var $lastActivityPanel = null;
+ while (true) {
+ var task = this.queuedTasksToOpen.shift();
+ if (task == undefined) {
+ this.openingQueuedTasks = false;
+ return;
+ }
+ if (task == 'subtask') {
+ var subtask = this.queuedTasksToOpen.shift();
+ $lastActivityPanel = this.showDrillDownTask(subtask, $lastActivityPanel);
+ } else {
+ log("unknown queued task for activities panel: "+task)
+ // skip it, just continue
+ }
+ }
+ },
+ beforeClose:function () {
+ this.collection.off("reset", this.renderOnLoad);
+ },
+ renderOnLoad: function() {
+ this.loaded = true;
+ this.render();
+ ViewUtils.cancelFadeOnceLoaded(this.table);
+ },
+ toggleAutoRefresh:function () {
+ ViewUtils.toggleAutoRefresh(this);
+ },
+ enableAutoRefresh: function(isEnabled) {
+ this.refreshActive = isEnabled
+ },
+ refreshNow: function() {
+ this.collection.fetch();
+ this.table.fnAdjustColumnSizing();
+ },
+ updateActivitiesNow: function() {
+ var that = this;
+ if (this.table == null || this.collection.length==0 || this.viewIsClosed) {
+ // nothing to do
+ } else {
+ 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"),
+ task.get("currentStatus")
+ ];
+ });
+ this.showDetailRow(true);
+ }
+ return this;
+ },
+ rowClick:function(evt) {
+ var row = $(evt.currentTarget).closest("tr");
+ var id = row.attr("id");
+ if (id==null)
+ // is the details row, ignore click here
+ return;
+ this.showDrillDownTask(id);
+ return;
+ },
+ showDrillDown: function(event) {
+ this.showDrillDownTask($(event.currentTarget).closest("td.row-expansion").attr("id"));
+ },
+ showDrillDownTask: function(taskId, optionalParent) {
+// log("showing initial drill down "+taskId)
+ var that = this;
+
+ var activityDetailsPanel = new ActivityDetailsView({
+ taskId: taskId,
+ tabView: that,
+ collection: this.collection,
+ breadcrumbs: ''
+ })
+ activityDetailsPanel.addToView(optionalParent || this.$(".activity-table"));
+ return activityDetailsPanel.$el;
+ },
+
+ showDetailRow: function(updateOnly) {
+ var id = this.selectedId,
+ that = this;
+ if (id==null) return;
+ var task = this.collection.get(id);
+ if (task==null) return;
+ if (!updateOnly) {
+ var html = _.template(ActivityRowDetailsHtml, {
+ task: task==null ? null : task.attributes,
+ link: that.model.getLinkByName("activities")+"/"+id,
+ updateOnly: updateOnly
+ })
+ $('tr#'+id).next().find('td.row-expansion').html(html)
+ $('tr#'+id).next().find('td.row-expansion').attr('id', id)
+ } else {
+ // just update
+ $('tr#'+id).next().find('.task-description').html(Util.escape(task.attributes.description))
+ }
+
+ var html = _.template(ActivityRowDetailsMainHtml, {
+ task: task==null ? null : task.attributes,
+ link: that.model.getLinkByName("activities")+"/"+id,
+ updateOnly: updateOnly
+ })
+ $('tr#'+id).next().find('.expansion-main').html(html)
+
+
+ if (!updateOnly) {
+ $('tr#'+id).next().find('.row-expansion .opened-row-details').hide()
+ $('tr#'+id).next().find('.row-expansion .opened-row-details').slideDown(300)
+ }
+ },
+ toggleFullDetail: function(evt) {
+ var i = $('.toggleFullDetail');
+ var id = i.closest("td.row-expansion").attr('id')
+ i.toggleClass('active')
+ if (i.hasClass('active'))
+ this.showFullActivity(id)
+ else
+ this.hideFullActivity(id)
+ },
+ showFullActivity: function(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);
+ }
+ });
+
+ return ActivitiesView;
+});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/entity-advanced.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/entity-advanced.js b/src/main/webapp/assets/js/view/entity-advanced.js
new file mode 100644
index 0000000..fe18430
--- /dev/null
+++ b/src/main/webapp/assets/js/view/entity-advanced.js
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Render entity advanced tab.
+ *
+ * @type {*}
+ */
+define(["underscore", "jquery", "backbone", "brooklyn", "brooklyn-utils", "view/viewutils",
+ "text!tpl/apps/advanced.html", "view/change-name-invoke", "view/add-child-invoke", "view/policy-new"
+], function(_, $, Backbone, Brooklyn, Util, ViewUtils,
+ AdvancedHtml, ChangeNameInvokeView, AddChildInvokeView, NewPolicyView) {
+ var EntityAdvancedView = Backbone.View.extend({
+ events: {
+ "click button#change-name": "showChangeNameModal",
+ "click button#add-child": "showAddChildModal",
+ "click button#add-new-policy": "showNewPolicyModal",
+ "click button#reset-problems": "confirmResetProblems",
+ "click button#expunge": "confirmExpunge",
+ "click button#unmanage": "confirmUnmanage",
+ "click #advanced-tab-error-closer": "closeAdvancedTabError"
+ },
+ template: _.template(AdvancedHtml),
+ initialize:function() {
+ _.bindAll(this);
+ this.$el.html(this.template());
+
+ this.model.on('change', this.modelChange, this);
+ this.modelChange();
+
+ ViewUtils.getRepeatedlyWithDelay(this, this.model.get('links').locations, this.renderLocationData);
+ ViewUtils.get(this, this.model.get('links').tags, this.renderTags);
+
+ ViewUtils.attachToggler(this.$el);
+ },
+ modelChange: function() {
+ this.$('#entity-name').html(Util.toDisplayString(this.model.get("name")));
+ ViewUtils.updateTextareaWithData($("#advanced-entity-json", this.$el), Util.toTextAreaString(this.model), true, false, 250, 600);
+ },
+ renderLocationData: function(data) {
+ ViewUtils.updateTextareaWithData($("#advanced-locations", this.$el), Util.toTextAreaString(data), true, false, 250, 600);
+ },
+ renderTags: function(data) {
+ var list = "";
+ for (tag in data)
+ list += "<div class='activity-tag-giftlabel'>"+Util.toDisplayString(data[tag])+"</div>";
+ if (!list) list = "No tags";
+ this.$('#advanced-entity-tags').html(list);
+ },
+ reload: function() {
+ this.model.fetch();
+ },
+
+ showModal: function(modal) {
+ if (this.activeModal)
+ this.activeModal.close();
+ this.activeModal = modal;
+ Brooklyn.view.showModalWith(modal);
+ },
+ showChangeNameModal: function() {
+ this.showModal(new ChangeNameInvokeView({
+ entity: this.model,
+ target:this
+ }));
+ },
+ showAddChildModal: function() {
+ this.showModal(new AddChildInvokeView({
+ entity: this.model,
+ target:this
+ }));
+ },
+ showNewPolicyModal: function () {
+ this.showModal(new NewPolicyView({
+ entity: this.model,
+ }));
+ },
+
+ confirmResetProblems: function () {
+ var entity = this.model.get("name");
+ var title = "Confirm the reset of problem indicators in " + entity;
+ var q = "<p>Are you sure you want to reset the problem indicators for this entity?</p>" +
+ "<p>If a problem has been fixed externally, but the fix is not being detected, this will clear problems. " +
+ "If the problem is not actually fixed, many feeds and enrichers will re-detect it, but note that some may not, " +
+ "and the entity may show as healthy when it is not." +
+ "</p>";
+ Brooklyn.view.requestConfirmation(q, title).done(this.doResetProblems);
+ },
+ doResetProblems: function() {
+ this.post(this.model.get('links').sensors+"/"+"service.notUp.indicators", {});
+ this.post(this.model.get('links').sensors+"/"+"service.problems", {});
+ },
+ post: function(url, data) {
+ var self = this;
+
+ $.ajax({
+ type: "POST",
+ url: url,
+ data: JSON.stringify(data),
+ contentType: "application/json",
+ success: function() {
+ self.reload();
+ },
+ error: function(response) {
+ self.showAdvancedTabError(Util.extractError(response, "Error contacting server", url));
+ }
+ });
+ },
+
+ confirmExpunge: function () {
+ var entity = this.model.get("name");
+ var title = "Confirm the expunging of " + entity;
+ var q = "<p>Are you certain you want to expunge this entity?</p>" +
+ "<p>When possible, Brooklyn will delete all of its resources.</p>" +
+ "<p><span class='label label-important'>Important</span> " +
+ "<b>This action is irreversible</b></p>";
+ this.unmanageAndOrExpunge(q, title, true);
+ },
+ confirmUnmanage: function () {
+ var entity = this.model.get("name");
+ var title = "Confirm the unmanagement of " + entity;
+ var q = "<p>Are you certain you want to unmanage this entity?</p>" +
+ "<p>Its resources will be left running.</p>" +
+ "<p><span class='label label-important'>Important</span> " +
+ "<b>This action is irreversible</b></p>";
+ this.unmanageAndOrExpunge(q, title, false);
+ },
+ unmanageAndOrExpunge: function (question, title, releaseResources) {
+ var self = this;
+ Brooklyn.view.requestConfirmation(question, title).done(function() {
+ return $.ajax({
+ type: "POST",
+ url: self.model.get("links").expunge + "?release=" + releaseResources + "&timeout=0",
+ contentType: "application/json"
+ }).done(function() {
+ self.trigger("entity.expunged");
+ }).fail(function() {
+ // (would just be connection error -- with timeout=0 we get a task even for invalid input)
+ self.showAdvancedTabError("Error connecting to Brooklyn server");
+
+ log("ERROR unmanaging/expunging");
+ log(data);
+ });
+ });
+ },
+
+ showAdvancedTabError: function(errorMessage) {
+ self.$("#advanced-tab-error-message").html(_.escape(errorMessage));
+ self.$("#advanced-tab-error-section").removeClass("hide");
+ },
+ closeAdvancedTabError: function() {
+ self.$("#advanced-tab-error-section").addClass("hide");
+ },
+
+ beforeClose:function() {
+ if (this.activeModal)
+ this.activeModal.close();
+ this.options.tabView.configView.close();
+ this.model.off();
+ }
+ });
+ return EntityAdvancedView;
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/entity-config.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/entity-config.js b/src/main/webapp/assets/js/view/entity-config.js
new file mode 100644
index 0000000..f517bcb
--- /dev/null
+++ b/src/main/webapp/assets/js/view/entity-config.js
@@ -0,0 +1,516 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Render entity config tab.
+ *
+ * @type {*}
+ */
+define([
+ "underscore", "jquery", "backbone", "brooklyn-utils", "zeroclipboard", "view/viewutils",
+ "model/config-summary", "text!tpl/apps/config.html", "text!tpl/apps/config-name.html",
+ "jquery-datatables", "datatables-extensions"
+], function (_, $, Backbone, Util, ZeroClipboard, ViewUtils, ConfigSummary, ConfigHtml, ConfigNameHtml) {
+
+ // TODO consider extracting all such usages to a shared ZeroClipboard wrapper?
+ ZeroClipboard.config({ moviePath: '//cdnjs.cloudflare.com/ajax/libs/zeroclipboard/1.3.1/ZeroClipboard.swf' });
+
+ var configHtml = _.template(ConfigHtml),
+ configNameHtml = _.template(ConfigNameHtml);
+
+ // TODO refactor to share code w entity-sensors.js
+ // in meantime, see notes there!
+ var EntityConfigView = Backbone.View.extend({
+ template: configHtml,
+ configMetadata:{},
+ refreshActive:true,
+ zeroClipboard: null,
+
+ events:{
+ 'click .refresh':'updateConfigNow',
+ 'click .filterEmpty':'toggleFilterEmpty',
+ 'click .toggleAutoRefresh':'toggleAutoRefresh',
+ 'click #config-table div.secret-info':'toggleSecrecyVisibility',
+
+ 'mouseup .valueOpen':'valueOpen',
+ 'mouseover #config-table tbody tr':'noteFloatMenuActive',
+ 'mouseout #config-table tbody tr':'noteFloatMenuSeemsInactive',
+ 'mouseover .floatGroup':'noteFloatMenuActive',
+ 'mouseout .floatGroup':'noteFloatMenuSeemsInactive',
+ 'mouseover .clipboard-item':'noteFloatMenuActiveCI',
+ 'mouseout .clipboard-item':'noteFloatMenuSeemsInactiveCI',
+ 'mouseover .hasFloatLeft':'showFloatLeft',
+ 'mouseover .hasFloatDown':'enterFloatDown',
+ 'mouseout .hasFloatDown':'exitFloatDown',
+ 'mouseup .light-popup-menu-item':'closeFloatMenuNow',
+ },
+
+ initialize:function () {
+ _.bindAll(this);
+ this.$el.html(this.template());
+
+ var that = this,
+ $table = this.$('#config-table');
+ that.table = ViewUtils.myDataTable($table, {
+ "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
+ $(nRow).attr('id', aData[0]);
+ $('td',nRow).each(function(i,v){
+ if (i==1) $(v).attr('class','config-value');
+ });
+ return nRow;
+ },
+ "aoColumnDefs": [
+ { // name (with tooltip)
+ "mRender": function ( data, type, row ) {
+ // name (column 1) should have tooltip title
+ var actions = that.getConfigActions(data.name);
+ // if data.description or .type is absent we get an error in html rendering (js)
+ // unless we set it explicitly (there is probably a nicer way to do this however?)
+ var context = _.extend(data, {
+ description: data['description'], type: data['type']});
+ return configNameHtml(context);
+ },
+ "aTargets": [ 1 ]
+ },
+ { // value
+ "mRender": function ( data, type, row ) {
+ var escapedValue = Util.toDisplayString(data);
+ if (type!='display')
+ return escapedValue;
+
+ var hasEscapedValue = (escapedValue!=null && (""+escapedValue).length > 0);
+ configName = row[0],
+ actions = that.getConfigActions(configName);
+
+ // NB: the row might not yet exist
+ var $row = $('tr[id="'+configName+'"]');
+
+ // datatables doesn't seem to expose any way to modify the html in place for a cell,
+ // so we rebuild
+
+ var result = "<span class='value'>"+(hasEscapedValue ? escapedValue : '')+"</span>";
+
+ var isSecret = Util.isSecret(configName);
+ if (isSecret) {
+ result += "<span class='secret-indicator'>(hidden)</span>";
+ }
+
+ if (actions.open)
+ result = "<a href='"+actions.open+"'>" + result + "</a>";
+ if (escapedValue==null || escapedValue.length < 3)
+ // include whitespace so we can click on it, if it's really small
+ result += " ";
+
+ var existing = $row.find('.dynamic-contents');
+ // for the json url, use the full url (relative to window.location.href)
+ var jsonUrl = actions.json ? new URI(actions.json).resolve(new URI(window.location.href)).toString() : null;
+ // prefer to update in place, so menus don't disappear, also more efficient
+ // (but if menu is changed, we do recreate it)
+ if (existing.length>0) {
+ if (that.checkFloatMenuUpToDate($row, actions.open, '.actions-open', 'open-target') &&
+ that.checkFloatMenuUpToDate($row, escapedValue, '.actions-copy') &&
+ that.checkFloatMenuUpToDate($row, actions.json, '.actions-json-open', 'open-target') &&
+ that.checkFloatMenuUpToDate($row, jsonUrl, '.actions-json-copy', 'copy-value')) {
+// log("updating in place "+configName)
+ existing.html(result);
+ return $row.find('td.config-value').html();
+ }
+ }
+
+ // build the menu - either because it is the first time, or the actions are stale
+// log("creating "+configName);
+
+ var downMenu = "";
+ if (actions.open)
+ downMenu += "<div class='light-popup-menu-item valueOpen actions-open' open-target='"+actions.open+"'>" +
+ "Open</div>";
+ if (hasEscapedValue) downMenu +=
+ "<div class='light-popup-menu-item handy valueCopy actions-copy clipboard-item'>Copy Value</div>";
+ if (actions.json) downMenu +=
+ "<div class='light-popup-menu-item handy valueOpen actions-json-open' open-target='"+actions.json+"'>" +
+ "Open REST Link</div>";
+ if (actions.json && hasEscapedValue) downMenu +=
+ "<div class='light-popup-menu-item handy valueCopy actions-json-copy clipboard-item' copy-value='"+
+ jsonUrl+"'>Copy REST Link</div>";
+ if (downMenu=="") {
+// log("no actions for "+configName);
+ downMenu +=
+ "<div class='light-popup-menu-item'>(no actions)</div>";
+ }
+ downMenu = "<div class='floatDown'><div class='light-popup'><div class='light-popup-body'>"
+ + downMenu +
+ "</div></div></div>";
+ result = "<span class='hasFloatLeft dynamic-contents'>" + result +
+ "</span>" +
+ "<div class='floatLeft'><span class='icon-chevron-down hasFloatDown'></span>" +
+ downMenu +
+ "</div>";
+ result = "<div class='floatGroup"+
+ (isSecret ? " secret-info" : "")+
+ "'>" + result + "</div>";
+ // also see updateFloatMenus which wires up the JS for these classes
+
+ return result;
+ },
+ "aTargets": [ 2 ]
+ },
+ // ID in column 0 is standard (assumed in ViewUtils)
+ { "bVisible": false, "aTargets": [ 0 ] }
+ ]
+ });
+
+ this.zeroClipboard = new ZeroClipboard();
+ this.zeroClipboard.on( "dataRequested" , function(client) {
+ try {
+ // the zeroClipboard instance is a singleton so check our scope first
+ if (!$(this).closest("#config-table").length) return;
+ var text = $(this).attr('copy-value');
+ if (!text) text = $(this).closest('.floatGroup').find('.value').text();
+
+// log("Copying config text '"+text+"' to clipboard");
+ client.setText(text);
+
+ // show the word "copied" for feedback;
+ // NB this occurs on mousedown, due to how flash plugin works
+ // (same style of feedback and interaction as github)
+ // the other "clicks" are now triggered by *mouseup*
+ var $widget = $(this);
+ var oldHtml = $widget.html();
+ $widget.html('<b>Copied!</b>');
+ // use a timeout to restore because mouseouts can leave corner cases (see history)
+ setTimeout(function() { $widget.html(oldHtml); }, 600);
+ } catch (e) {
+ log("Zeroclipboard failure; falling back to prompt mechanism");
+ log(e);
+ Util.promptCopyToClipboard(text);
+ }
+ });
+ // these seem to arrive delayed sometimes, so we also work with the clipboard-item class events
+ this.zeroClipboard.on( "mouseover", function() { that.noteFloatMenuZeroClipboardItem(true, this); } );
+ this.zeroClipboard.on( "mouseout", function() { that.noteFloatMenuZeroClipboardItem(false, this); } );
+ this.zeroClipboard.on( "mouseup", function() { that.closeFloatMenuNow(); } );
+
+ ViewUtils.addFilterEmptyButton(this.table);
+ ViewUtils.addAutoRefreshButton(this.table);
+ ViewUtils.addRefreshButton(this.table);
+ this.loadConfigMetadata();
+ this.updateConfigPeriodically();
+ this.toggleFilterEmpty();
+ return this;
+ },
+
+ beforeClose: function () {
+ if (this.zeroClipboard) {
+ this.zeroClipboard.destroy();
+ }
+ },
+
+ floatMenuActive: false,
+ lastFloatMenuRowId: null,
+ lastFloatFocusInTextForEventUnmangling: null,
+ updateFloatMenus: function() {
+ $('#config-table *[rel="tooltip"]').tooltip();
+ this.zeroClipboard.clip( $('.valueCopy') );
+ },
+ showFloatLeft: function(event) {
+ this.noteFloatMenuFocusChange(true, event, "show-left");
+ this.showFloatLeftOf($(event.currentTarget));
+ },
+ showFloatLeftOf: function($hasFloatLeft) {
+ $hasFloatLeft.next('.floatLeft').show();
+ },
+ enterFloatDown: function(event) {
+ this.noteFloatMenuFocusChange(true, event, "show-down");
+// log("entering float down");
+ var fdTarget = $(event.currentTarget);
+// log( fdTarget );
+ this.floatDownFocus = fdTarget;
+ var that = this;
+ setTimeout(function() {
+ that.showFloatDownOf( fdTarget );
+ }, 200);
+ },
+ exitFloatDown: function(event) {
+// log("exiting float down");
+ this.floatDownFocus = null;
+ },
+ showFloatDownOf: function($hasFloatDown) {
+ if ($hasFloatDown != this.floatDownFocus) {
+// log("float down did not hover long enough");
+ return;
+ }
+ var down = $hasFloatDown.next('.floatDown');
+ down.show();
+ $('.light-popup', down).show(2000);
+ },
+ noteFloatMenuActive: function(focus) {
+ this.noteFloatMenuFocusChange(true, focus, "menu");
+
+ // remove dangling zc events (these don't always get removed, apparent bug in zc event framework)
+ // this causes it to flash sometimes but that's better than leaving the old item highlighted
+ if (focus.toElement && $(focus.toElement).hasClass('clipboard-item')) {
+ // don't remove it
+ } else {
+ var zc = $(focus.target).closest('.floatGroup').find('div.zeroclipboard-is-hover');
+ zc.removeClass('zeroclipboard-is-hover');
+ }
+ },
+ noteFloatMenuSeemsInactive: function(focus) { this.noteFloatMenuFocusChange(false, focus, "menu"); },
+ noteFloatMenuActiveCI: function(focus) { this.noteFloatMenuFocusChange(true, focus, "menu-clip-item"); },
+ noteFloatMenuSeemsInactiveCI: function(focus) { this.noteFloatMenuFocusChange(false, focus, "menu-clip-item"); },
+ noteFloatMenuZeroClipboardItem: function(seemsActive,focus) {
+ this.noteFloatMenuFocusChange(seemsActive, focus, "clipboard");
+ if (seemsActive) {
+ // make the table row highlighted (as the default hover event is lost)
+ // we remove it when the float group goes away
+ $(focus).closest('tr').addClass('zeroclipboard-is-hover');
+ } else {
+ // sometimes does not get removed by framework - though this doesn't seem to help
+ // as you can see by logging this before and after:
+// log(""+$(focus).attr('class'))
+ // the problem is that the framework seems sometime to trigger this event before adding the class
+ // see in noteFloatMenuActive where we do a different check
+ $(focus).removeClass('zeroclipboard-is-hover');
+ }
+ },
+ noteFloatMenuFocusChange: function(seemsActive, focus, caller) {
+// log(""+new Date().getTime()+" note active "+caller+" "+seemsActive);
+ var delayCheckFloat = true;
+ var focusRowId = null;
+ var focusElement = null;
+ if (focus) {
+ focusElement = focus.target ? focus.target : focus;
+ if (seemsActive) {
+ this.lastFloatFocusInTextForEventUnmangling = $(focusElement).text();
+ focusRowId = focus.target ? $(focus.target).closest('tr').attr('id') : $(focus).closest('tr').attr('id');
+ if (this.floatMenuActive && focusRowId==this.lastFloatMenuRowId) {
+ // lastFloatMenuRowId has not changed, when moving within a floatgroup
+ // (but we still get mouseout events when the submenu changes)
+// log("redundant mousein from "+ focusRowId );
+ return;
+ }
+ } else {
+ // on mouseout, skip events which are bogus
+ // first, if the toElement is in the same floatGroup
+ focusRowId = focus.toElement ? $(focus.toElement).closest('tr').attr('id') : null;
+ if (focusRowId==this.lastFloatMenuRowId) {
+ // lastFloatMenuRowId has not changed, when moving within a floatgroup
+ // (but we still get mouseout events when the submenu changes)
+// log("skipping, internal mouseout from "+ focusRowId );
+ return;
+ }
+ // check (a) it is the 'out' event corresponding to the most recent 'in'
+ // (because there is a race where it can say in1, in2, out1 rather than in1, out2, in2
+ if ($(focusElement).text() != this.lastFloatFocusInTextForEventUnmangling) {
+// log("skipping, not most recent mouseout from "+ focusRowId );
+ return;
+ }
+ if (focus.toElement) {
+ if ($(focus.toElement).hasClass('global-zeroclipboard-container')) {
+// log("skipping out, as we are moving to clipboard container");
+ return;
+ }
+ if (focus.toElement.name && focus.toElement.name=="global-zeroclipboard-flash-bridge") {
+// log("skipping out, as we are moving to clipboard movie");
+ return;
+ }
+ }
+ }
+ }
+// log( "moving to "+focusRowId );
+ if (seemsActive && focusRowId) {
+// log("setting lastFloat when "+this.floatMenuActive + ", from "+this.lastFloatMenuRowId );
+ if (this.lastFloatMenuRowId != focusRowId) {
+ if (this.lastFloatMenuRowId) {
+ // the floating menu has changed, hide the old
+// log("hiding old menu on float-focus change");
+ this.closeFloatMenuNow();
+ }
+ }
+ // now show the new, if possible (might happen multiple times, but no matter
+ if (focusElement) {
+// log("ensuring row "+focusRowId+" is showing on change");
+ this.showFloatLeftOf($(focusElement).closest('tr').find('.hasFloatLeft'));
+ this.lastFloatMenuRowId = focusRowId;
+ } else {
+ this.lastFloatMenuRowId = null;
+ }
+ }
+ this.floatMenuActive = seemsActive;
+ if (!seemsActive) {
+ this.scheduleCheckFloatMenuNeedsHiding(delayCheckFloat);
+ }
+ },
+ scheduleCheckFloatMenuNeedsHiding: function(delayCheckFloat) {
+ if (delayCheckFloat) {
+ this.checkTime = new Date().getTime()+299;
+ setTimeout(this.checkFloatMenuNeedsHiding, 300);
+ } else {
+ this.checkTime = new Date().getTime()-1;
+ this.checkFloatMenuNeedsHiding();
+ }
+ },
+ closeFloatMenuNow: function() {
+// log("closing float menu due do direct call (eg click)");
+ this.checkTime = new Date().getTime()-1;
+ this.floatMenuActive = false;
+ this.checkFloatMenuNeedsHiding();
+ },
+ checkFloatMenuNeedsHiding: function() {
+// log(""+new Date().getTime()+" checking float menu - "+this.floatMenuActive);
+ if (new Date().getTime() <= this.checkTime) {
+// log("aborting check as another one scheduled");
+ return;
+ }
+
+ // we use a flag to determine whether to hide the float menu
+ // because the embedded zero-clipboard flash objects cause floatGroup
+ // to get a mouseout event when the "Copy" menu item is hovered
+ if (!this.floatMenuActive) {
+// log("HIDING FLOAT MENU")
+ $('.floatLeft').hide();
+ $('.floatDown').hide();
+ $('.zeroclipboard-is-hover').removeClass('zeroclipboard-is-hover');
+ lastFloatMenuRowId = null;
+ } else {
+// log("we're still in")
+ }
+ },
+ valueOpen: function(event) {
+ window.open($(event.target).attr('open-target'),'_blank');
+ },
+
+ render: function() {
+ return this;
+ },
+ checkFloatMenuUpToDate: function($row, actionValue, actionSelector, actionAttribute) {
+ if (typeof actionValue === 'undefined' || actionValue==null || actionValue=="") {
+ if ($row.find(actionSelector).length==0) return true;
+ } else {
+ if (actionAttribute) {
+ if ($row.find(actionSelector).attr(actionAttribute)==actionValue) return true;
+ } else {
+ if ($row.find(actionSelector).length>0) return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Returns the actions loaded to view.configMetadata[name].actions
+ * for the given name, or an empty object.
+ */
+ getConfigActions: function(configName) {
+ var allMetadata = this.configMetadata || {};
+ var metadata = allMetadata[configName] || {};
+ return metadata.actions || {};
+ },
+
+ toggleFilterEmpty: function() {
+ ViewUtils.toggleFilterEmpty(this.$('#config-table'), 2);
+ return this;
+ },
+
+ toggleAutoRefresh: function() {
+ ViewUtils.toggleAutoRefresh(this);
+ return this;
+ },
+
+ enableAutoRefresh: function(isEnabled) {
+ this.refreshActive = isEnabled;
+ return this;
+ },
+
+ toggleSecrecyVisibility: function(event) {
+ $(event.target).closest('.secret-info').toggleClass('secret-revealed');
+ },
+
+ /**
+ * Loads current values for all config on an entity and updates config table.
+ */
+ isRefreshActive: function() { return this.refreshActive; },
+ updateConfigNow:function () {
+ var that = this;
+ ViewUtils.get(that, that.model.getConfigUpdateUrl(), that.updateWithData,
+ { enablement: that.isRefreshActive });
+ },
+ updateConfigPeriodically:function () {
+ var that = this;
+ ViewUtils.getRepeatedlyWithDelay(that, that.model.getConfigUpdateUrl(), function(data) { that.updateWithData(data); },
+ { enablement: that.isRefreshActive });
+ },
+ updateWithData: function (data) {
+ var that = this;
+ $table = that.$('#config-table');
+ var options = {};
+
+ if (that.fullRedraw) {
+ options.refreshAllRows = true;
+ that.fullRedraw = false;
+ }
+ ViewUtils.updateMyDataTable($table, data, function(value, name) {
+ var metadata = that.configMetadata[name];
+ if (metadata==null) {
+ // kick off reload metadata when this happens (new config for which no metadata known)
+ // but only if we haven't loaded metadata for a while
+ metadata = { 'name':name };
+ that.configMetadata[name] = metadata;
+ that.loadConfigMetadataIfStale(name, 10000);
+ }
+ return [name, metadata, value];
+ }, options);
+
+ that.updateFloatMenus();
+ },
+
+ loadConfigMetadata: function() {
+ var url = this.model.getLinkByName('config'),
+ that = this;
+ that.lastConfigMetadataLoadTime = new Date().getTime();
+ $.get(url, function (data) {
+ _.each(data, function(config) {
+ var actions = {};
+ _.each(config.links, function(v, k) {
+ if (k.slice(0, 7) == "action:") {
+ actions[k.slice(7)] = v;
+ }
+ });
+ that.configMetadata[config.name] = {
+ name: config.name,
+ description: config.description,
+ actions: actions,
+ type: config.type
+ };
+ });
+ that.fullRedraw = true;
+ that.updateConfigNow();
+ that.table.find('*[rel="tooltip"]').tooltip();
+ });
+ return this;
+ },
+
+ loadConfigMetadataIfStale: function(configName, recency) {
+ var that = this;
+ if (!that.lastConfigMetadataLoadTime || that.lastConfigMetadataLoadTime + recency < new Date().getTime()) {
+// log("reloading metadata because new config "+configName+" identified")
+ that.loadConfigMetadata();
+ }
+ }
+ });
+ return EntityConfigView;
+});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/entity-details.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/entity-details.js b/src/main/webapp/assets/js/view/entity-details.js
new file mode 100644
index 0000000..f54c572
--- /dev/null
+++ b/src/main/webapp/assets/js/view/entity-details.js
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Renders details information about an application (sensors, summary, effectors, etc.).
+ *
+ * Options preselectTab (e.g. 'activities') and preselectTabDetails ('subtasks/1234') can be set
+ * before a render to cause the given tab / details to be opened.
+ *
+ * @type {*}
+ */
+define([
+ "underscore", "jquery", "backbone", "./entity-summary",
+ "./entity-sensors", "./entity-effectors", "./entity-policies",
+ "./entity-activities", "./entity-advanced", "model/task-summary", "text!tpl/apps/details.html"
+], function (_, $, Backbone, SummaryView, SensorsView, EffectorsView, PoliciesView, ActivitiesView, AdvancedView, TaskSummary, DetailsHtml) {
+
+ var EntityDetailsView = Backbone.View.extend({
+ template:_.template(DetailsHtml),
+ events:{
+ 'click .entity-tabs a':'tabSelected'
+ },
+ initialize:function () {
+ var self = this;
+ var tasks = new TaskSummary.Collection;
+
+ this.$el.html(this.template({}))
+ this.sensorsView = new SensorsView({
+ model:this.model,
+ tabView:this,
+ })
+ this.effectorsView = new EffectorsView({
+ model:this.model,
+ tabView:this,
+ })
+ this.policiesView = new PoliciesView({
+ model:this.model,
+ tabView:this,
+ })
+ this.activitiesView = new ActivitiesView({
+ model:this.model,
+ tabView:this,
+ collection:tasks
+ })
+ // summary comes after others because it uses the tasks
+ this.summaryView = new SummaryView({
+ model:this.model,
+ tabView:this,
+ application:this.options.application,
+ tasks:tasks,
+ })
+ this.advancedView = new AdvancedView({
+ model: this.model,
+ tabView:this,
+ application:this.options.application
+ });
+ // propagate to app tree view
+ this.advancedView.on("entity.expunged", function() { self.trigger("entity.expunged"); })
+
+ this.$("#summary").html(this.summaryView.render().el);
+ this.$("#sensors").html(this.sensorsView.render().el);
+ this.$("#effectors").html(this.effectorsView.render().el);
+ this.$("#policies").html(this.policiesView.render().el);
+ this.$("#activities").html(this.activitiesView.render().el);
+ this.$("#advanced").html(this.advancedView.render().el);
+ },
+ beforeClose:function () {
+ this.summaryView.close();
+ this.sensorsView.close();
+ this.effectorsView.close();
+ this.policiesView.close();
+ this.activitiesView.close();
+ this.advancedView.close();
+ },
+ getEntityHref: function() {
+ return $("#app-tree .entity_tree_node_wrapper.active a").attr("href");
+ },
+ render: function(optionalParent) {
+ this.summaryView.render()
+ this.sensorsView.render()
+ this.effectorsView.render()
+ this.policiesView.render()
+ this.activitiesView.render()
+ this.advancedView.render()
+
+ if (optionalParent) {
+ optionalParent.html(this.el)
+ }
+ var entityHref = this.getEntityHref();
+ if (entityHref) {
+ $("a[data-toggle='tab']").each(function(i,a) {
+ $(a).attr('href',entityHref+"/"+$(a).attr("data-target").slice(1));
+ });
+ } else {
+ log("could not find entity href for tab");
+ }
+ if (this.options.preselectTab) {
+ var tabLink = this.$('a[data-target="#'+this.options.preselectTab+'"]');
+ var showFn = function() { tabLink.tab('show'); };
+ if (optionalParent) showFn();
+ else _.defer(showFn);
+ }
+ return this;
+ },
+ tabSelected: function(event) {
+ // TODO: the bootstrap JS code still prevents shift-click from working
+ // have to add the following logic to bootstrap tab click handler also
+// if (event.metaKey || event.shiftKey)
+// // trying to open in a new tab, do not act on it here!
+// return;
+ event.preventDefault();
+
+ var tabName = $(event.currentTarget).attr("data-target").slice(1);
+ var route = this.getTab(tabName);
+ if (route) {
+ if (route[0]=='#') route = route.substring(1);
+ Backbone.history.navigate(route);
+ }
+ // caller will ensure tab is shown
+ },
+ getTab: function(tabName, entityId, entityHref) {
+ if (!entityHref) {
+ if (entityId) {
+ entityHref = this.getEntityHref();
+ if (!entityHref.endsWith(entityId)) {
+ lastSlash = entityHref.lastIndexOf('/');
+ if (lastSlash>=0) {
+ entityHref = entityHref.substring(0, lastSlash+1) + '/' + entityId;
+ } else {
+ log("malformed entityHref when opening tab: "+entityHref)
+ entityHref = this.getEntityHref();
+ }
+ }
+ } else {
+ entityHref = this.getEntityHref();
+ }
+ }
+ if (entityHref && tabName)
+ return entityHref+"/"+tabName;
+ return null;
+ },
+ /** for tabs to redirect to other tabs; entityId and entityHref are optional (can supply either, or null to use current entity);
+ * tabPath is e.g. 'sensors' or 'activities/subtask/1234' */
+ openTab: function(tabPath, entityId, entityHref) {
+ var route = this.getTab(tabPath, entityId, entityHref);
+ if (!route) return;
+ if (route[0]=='#') route = route.substring(1);
+ Backbone.history.navigate(route);
+
+ tabPaths = tabPath.split('/');
+ if (!tabPaths) return;
+ var tabName = tabPaths.shift();
+ if (!tabName)
+ // ignore leading /
+ tabName = tabPaths.shift();
+ if (!tabName) return;
+
+ this.options.preselectTab = tabName;
+ if (tabPaths)
+ this.options.preselectTabDetails = tabPaths.join('/');
+ this.render();
+ }
+ });
+ return EntityDetailsView;
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/entity-effectors.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/entity-effectors.js b/src/main/webapp/assets/js/view/entity-effectors.js
new file mode 100644
index 0000000..974fbec
--- /dev/null
+++ b/src/main/webapp/assets/js/view/entity-effectors.js
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Render the effectors tab.You must supply the model and optionally the element
+ * on which the view binds itself.
+ *
+ * @type {*}
+ */
+define([
+ "underscore", "jquery", "backbone", "view/viewutils", "model/effector-summary",
+ "view/effector-invoke", "text!tpl/apps/effector.html", "text!tpl/apps/effector-row.html", "bootstrap"
+], function (_, $, Backbone, ViewUtils, EffectorSummary, EffectorInvokeView, EffectorHtml, EffectorRowHtml) {
+
+ var EntityEffectorsView = Backbone.View.extend({
+ template:_.template(EffectorHtml),
+ effectorRow:_.template(EffectorRowHtml),
+ events:{
+ "click .show-effector-modal":"showEffectorModal"
+ },
+ initialize:function () {
+ this.$el.html(this.template({}))
+ var that = this
+ this._effectors = new EffectorSummary.Collection()
+ // fetch the list of effectors and create a view for each one
+ this._effectors.url = this.model.getLinkByName("effectors")
+ that.loadedData = false;
+ ViewUtils.fadeToIndicateInitialLoad(this.$('#effectors-table'));
+ this.$(".has-no-effectors").hide();
+
+ this._effectors.fetch({success:function () {
+ that.loadedData = true;
+ that.render()
+ ViewUtils.cancelFadeOnceLoaded(that.$('#effectors-table'));
+ }})
+ // attach a fetch simply to fade this tab when not available
+ // (the table is statically rendered)
+ ViewUtils.fetchRepeatedlyWithDelay(this, this._effectors, { period: 10*1000 })
+ },
+ render:function () {
+ if (this.viewIsClosed)
+ return;
+ var that = this
+ var $tableBody = this.$('#effectors-table tbody').empty()
+ if (this._effectors.length==0) {
+ if (that.loadedData)
+ this.$(".has-no-effectors").show();
+ } else {
+ this.$(".has-no-effectors").hide();
+ this._effectors.each(function (effector) {
+ $tableBody.append(that.effectorRow({
+ name:effector.get("name"),
+ description:effector.get("description"),
+ // cid is mapped to id (here) which is mapped to name (in Effector.Summary),
+ // so it is consistent across resets
+ cid:effector.id
+ }))
+ })
+ }
+ return this
+ },
+ showEffectorModal:function (eventName) {
+ // get the model that we need to show, create its view and show it
+ var cid = $(eventName.currentTarget).attr("id")
+ var effectorModel = this._effectors.get(cid);
+ var modal = new EffectorInvokeView({
+ el:"#effector-modal",
+ model:effectorModel,
+ entity:this.model,
+ tabView:this.options.tabView,
+ openTask:true
+ })
+ modal.render().$el.modal('show')
+ }
+ })
+ return EntityEffectorsView
+})
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/entity-policies.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/entity-policies.js b/src/main/webapp/assets/js/view/entity-policies.js
new file mode 100644
index 0000000..74ba885
--- /dev/null
+++ b/src/main/webapp/assets/js/view/entity-policies.js
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+/**
+ * Render the policies tab. You must supply the model and optionally the element
+ * on which the view binds itself.
+ */
+define([
+ "underscore", "jquery", "backbone", "brooklyn",
+ "model/policy-summary", "model/policy-config-summary",
+ "view/viewutils", "view/policy-config-invoke", "view/policy-new",
+ "text!tpl/apps/policy.html", "text!tpl/apps/policy-row.html", "text!tpl/apps/policy-config-row.html",
+ "jquery-datatables", "datatables-extensions"
+], function (_, $, Backbone, Brooklyn,
+ PolicySummary, PolicyConfigSummary,
+ ViewUtils, PolicyConfigInvokeView, NewPolicyView,
+ PolicyHtml, PolicyRowHtml, PolicyConfigRowHtml) {
+
+ var EntityPoliciesView = Backbone.View.extend({
+
+ template: _.template(PolicyHtml),
+ policyRow: _.template(PolicyRowHtml),
+
+ events:{
+ 'click .refresh':'refreshPolicyConfigNow',
+ 'click .filterEmpty':'toggleFilterEmpty',
+ "click #policies-table tr":"rowClick",
+ "click .policy-start":"callStart",
+ "click .policy-stop":"callStop",
+ "click .policy-destroy":"callDestroy",
+ "click .show-policy-config-modal":"showPolicyConfigModal",
+ "click .add-new-policy": "showNewPolicyModal"
+ },
+
+ initialize:function () {
+ _.bindAll(this)
+ this.$el.html(this.template({ }));
+ var that = this;
+ // fetch the list of policies and create a row for each one
+ that._policies = new PolicySummary.Collection();
+ that._policies.url = that.model.getLinkByName("policies");
+
+ this.loadedData = false;
+ ViewUtils.fadeToIndicateInitialLoad(this.$('#policies-table'));
+ that.render();
+ this._policies.on("all", this.render, this)
+ ViewUtils.fetchRepeatedlyWithDelay(this, this._policies, {
+ doitnow: true,
+ success: function () {
+ that.loadedData = true;
+ ViewUtils.cancelFadeOnceLoaded(that.$('#policies-table'));
+ }});
+ },
+
+ render:function () {
+ if (this.viewIsClosed)
+ return;
+ var that = this,
+ $tbody = this.$('#policies-table tbody').empty();
+ if (that._policies.length==0) {
+ if (this.loadedData)
+ this.$(".has-no-policies").show();
+ this.$("#policy-config").hide();
+ this.$("#policy-config-none-selected").hide();
+ } else {
+ this.$(".has-no-policies").hide();
+ that._policies.each(function (policy) {
+ // TODO better to use datatables, and a json array, as we do elsewhere
+ $tbody.append(that.policyRow({
+ cid:policy.get("id"),
+ name:policy.get("name"),
+ state:policy.get("state"),
+ summary:policy
+ }));
+ if (that.activePolicy) {
+ that.$("#policies-table tr[id='"+that.activePolicy+"']").addClass("selected");
+ that.showPolicyConfig(that.activePolicy);
+ that.refreshPolicyConfig();
+ } else {
+ that.$("#policy-config").hide();
+ that.$("#policy-config-none-selected").show();
+ }
+ });
+ }
+ return that;
+ },
+
+ toggleFilterEmpty:function() {
+ ViewUtils.toggleFilterEmpty($('#policy-config-table'), 2);
+ },
+
+ refreshPolicyConfigNow:function () {
+ this.refreshPolicyConfig();
+ },
+
+ rowClick:function(evt) {
+ evt.stopPropagation();
+ var row = $(evt.currentTarget).closest("tr"),
+ id = row.attr("id"),
+ policy = this._policies.get(id);
+ $("#policies-table tr").removeClass("selected");
+ if (this.activePolicy == id) {
+ // deselected
+ this.activePolicy = null;
+ this._config = null;
+ $("#policy-config-table").dataTable().fnDestroy();
+ $("#policy-config").slideUp(100);
+ $("#policy-config-none-selected").slideDown(100);
+ } else {
+ row.addClass("selected");
+ // fetch the list of policy config entries
+ this._config = new PolicyConfigSummary.Collection();
+ this._config.url = policy.getLinkByName("config");
+ ViewUtils.fadeToIndicateInitialLoad($('#policy-config-table'));
+ this.showPolicyConfig(id);
+ var that = this;
+ this._config.fetch().done(function () {
+ that.showPolicyConfig(id);
+ ViewUtils.cancelFadeOnceLoaded($('#policy-config-table'))
+ });
+ }
+ },
+
+ showPolicyConfig:function (activePolicyId) {
+ var that = this;
+ if (activePolicyId != null && that.activePolicy != activePolicyId) {
+ // TODO better to use a json array, as we do elsewhere
+ var $table = $('#policy-config-table'),
+ $tbody = $table.find('tbody');
+ $table.dataTable().fnClearTable();
+ $("#policy-config-none-selected").slideUp(100);
+ if (that._config.length==0) {
+ $(".has-no-policy-config").show();
+ } else {
+ $(".has-no-policy-config").hide();
+ that.activePolicy = activePolicyId;
+ var policyConfigRow = _.template(PolicyConfigRowHtml);
+ that._config.each(function (config) {
+ $tbody.append(policyConfigRow({
+ cid:config.cid,
+ name:config.get("name"),
+ description:config.get("description"),
+ type:config.get("type"),
+ reconfigurable:config.get("reconfigurable"),
+ link:config.getLinkByName('self'),
+ value: config.get("defaultValue")
+ }));
+ $tbody.find('*[rel="tooltip"]').tooltip();
+ });
+ that.currentStateUrl = that._policies.get(that.activePolicy).getLinkByName("config") + "/current-state";
+ $("#policy-config").slideDown(100);
+ $table.slideDown(100);
+ ViewUtils.myDataTable($table, {
+ "bAutoWidth": false,
+ "aoColumns" : [
+ { sWidth: '220px' },
+ { sWidth: '240px' },
+ { sWidth: '25px' }
+ ]
+ });
+ $table.dataTable().fnAdjustColumnSizing();
+ }
+ }
+ that.refreshPolicyConfig();
+ },
+
+ refreshPolicyConfig:function() {
+ var that = this;
+ if (that.viewIsClosed || !that.currentStateUrl) return;
+ var $table = that.$('#policy-config-table').dataTable(),
+ $rows = that.$("tr.policy-config-row");
+ $.get(that.currentStateUrl, function (data) {
+ if (that.viewIsClosed) return;
+ // iterate over the sensors table and update each sensor
+ $rows.each(function (index, row) {
+ var key = $(this).find(".policy-config-name").text();
+ var v = data[key];
+ if (v !== undefined) {
+ $table.fnUpdate(_.escape(v), row, 1, false);
+ }
+ });
+ });
+ $table.dataTable().fnStandingRedraw();
+ },
+
+ showPolicyConfigModal: function (evt) {
+ var cid = $(evt.currentTarget).attr("id");
+ var currentValue = $(evt.currentTarget)
+ .parent().parent()
+ .find(".policy-config-value")
+ .text();
+ Brooklyn.view.showModalWith(new PolicyConfigInvokeView({
+ model: this._config.get(cid),
+ policy: this.model,
+ currentValue: currentValue
+ }));
+ },
+
+ showNewPolicyModal: function () {
+ var self = this;
+ Brooklyn.view.showModalWith(new NewPolicyView({
+ entity: this.model,
+ onSave: function (policy) {
+ console.log("New policy", policy);
+ self._policies.add(policy);
+ }
+ }));
+ },
+
+ callStart:function(event) { this.doPost(event, "start"); },
+ callStop:function(event) { this.doPost(event, "stop"); },
+ callDestroy:function(event) { this.doPost(event, "destroy"); },
+ doPost:function(event, linkname) {
+ event.stopPropagation();
+ var that = this,
+ url = $(event.currentTarget).attr("link");
+ $.ajax({
+ type:"POST",
+ url:url,
+ success:function() {
+ that._policies.fetch();
+ }
+ });
+ }
+
+ });
+
+ return EntityPoliciesView;
+});