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:22 UTC
[04/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/router.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/router.js b/src/main/webapp/assets/js/router.js
new file mode 100644
index 0000000..d80d35c
--- /dev/null
+++ b/src/main/webapp/assets/js/router.js
@@ -0,0 +1,240 @@
+/*
+ * 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.
+ */
+define([
+ "brooklyn", "underscore", "jquery", "backbone",
+ "model/application", "model/app-tree", "model/location",
+ "model/server-extended-status",
+ "view/home", "view/application-explorer", "view/catalog", "view/script-groovy",
+ "text!tpl/help/page.html","text!tpl/labs/page.html", "text!tpl/home/server-caution.html"
+], function (Brooklyn, _, $, Backbone,
+ Application, AppTree, Location,
+ serverStatus,
+ HomeView, ExplorerView, CatalogView, ScriptGroovyView,
+ HelpHtml, LabsHtml, ServerCautionHtml) {
+
+ var ServerCautionOverlay = Backbone.View.extend({
+ template: _.template(ServerCautionHtml),
+ scheduledRedirect: false,
+ initialize: function() {
+ var that = this;
+ this.carryOnRegardless = false;
+ _.bindAll(this);
+ serverStatus.addCallback(this.renderAndAddCallback);
+ },
+ renderAndAddCallback: function() {
+ this.renderOnUpdate();
+ serverStatus.addCallback(this.renderAndAddCallback);
+ },
+ renderOnUpdate: function() {
+ var that = this;
+ if (this.carryOnRegardless) return this.renderEmpty();
+
+ var state = {
+ loaded: serverStatus.loaded,
+ up: serverStatus.isUp(),
+ shuttingDown: serverStatus.isShuttingDown(),
+ healthy: serverStatus.isHealthy(),
+ master: serverStatus.isMaster(),
+ masterUri: serverStatus.getMasterUri(),
+ };
+
+ if (state.loaded && state.up && state.healthy && state.master) {
+ // this div shows nothing in normal operation
+ return this.renderEmpty();
+ }
+
+ this.warningActive = true;
+ this.$el.html(this.template(state));
+ $('#application-content').fadeTo(500,0.1);
+ this.$el.fadeTo(200,1);
+
+ $("#dismiss-standby-warning", this.$el).click(function() {
+ that.carryOnRegardless = true;
+ if (that.redirectPending) {
+ log("Cancelling redirect, using this non-master instance");
+ clearTimeout(that.redirectPending);
+ that.redirectPending = null;
+ }
+ that.renderOnUpdate();
+ });
+
+ if (!state.master && state.masterUri) {
+ if (!this.scheduledRedirect && !this.redirectPending) {
+ log("Not master; will redirect shortly to: "+state.masterUri);
+ var destination = state.masterUri + "#" + Backbone.history.fragment;
+ var time = 10;
+ this.scheduledRedirect = true;
+ log("Redirecting to " + destination + " in " + time + " seconds");
+ this.redirectPending = setTimeout(function () {
+ // re-check, in case the server's status changed in the wait
+ if (!serverStatus.isMaster()) {
+ if (that.redirectPending) {
+ window.location.href = destination;
+ } else {
+ log("Cancelled redirect, using this non-master instance");
+ }
+ } else {
+ log("Cancelled redirect, this instance is now master");
+ }
+ }, time * 1000);
+ }
+ }
+ return this;
+ },
+ renderEmpty: function() {
+ var that = this;
+ this.warningActive = false;
+ this.$el.fadeTo(200,0.2, function() {
+ if (!that.warningActive)
+ that.$el.empty();
+ });
+ $('#application-content').fadeTo(200,1);
+ return this;
+ },
+ beforeClose: function() {
+ this.stopListening();
+ },
+ warnIfNotLoaded: function() {
+ if (!this.loaded)
+ this.renderOnUpdate();
+ }
+ });
+ // look for ha-standby-overlay for compatibility with older index.html copies
+ var serverCautionOverlay = new ServerCautionOverlay({ el: $("#server-caution-overlay").length ? $("#server-caution-overlay") : $("#ha-standby-overlay")});
+ serverCautionOverlay.render();
+
+ var Router = Backbone.Router.extend({
+ routes:{
+ 'v1/home/*trail':'homePage',
+ 'v1/applications/:app/entities/*trail':'applicationsPage',
+ 'v1/applications/*trail':'applicationsPage',
+ 'v1/applications':'applicationsPage',
+ 'v1/locations':'catalogPage',
+ 'v1/catalog/:kind(/:id)':'catalogPage',
+ 'v1/catalog':'catalogPage',
+ 'v1/script/groovy':'scriptGroovyPage',
+ 'v1/help':'helpPage',
+ 'labs':'labsPage',
+ '*path':'defaultRoute'
+ },
+
+ showView: function(selector, view) {
+ // close the previous view - does binding clean-up and avoids memory leaks
+ if (this.currentView) {
+ this.currentView.close();
+ }
+ // render the view inside the selector element
+ $(selector).html(view.render().el);
+ this.currentView = view;
+ return view;
+ },
+
+ defaultRoute: function() {
+ this.homePage('auto')
+ },
+
+ applications: new Application.Collection,
+ appTree: new AppTree.Collection,
+ locations: new Location.Collection,
+
+ homePage:function (trail) {
+ var that = this;
+ var veryFirstViewLoad, homeView;
+ // render the page after we fetch the collection -- no rendering on error
+ function render() {
+ homeView = new HomeView({
+ collection:that.applications,
+ locations:that.locations,
+ cautionOverlay:serverCautionOverlay,
+ appRouter:that
+ });
+ veryFirstViewLoad = !that.currentView;
+ that.showView("#application-content", homeView);
+ }
+ this.applications.fetch({success:function () {
+ render();
+ // show add application wizard if none already exist and this is the first page load
+ if ((veryFirstViewLoad && trail=='auto' && that.applications.isEmpty()) || (trail=='add_application') ) {
+ if (serverStatus.isMaster()) {
+ homeView.createApplication();
+ }
+ }
+ }, error: render});
+ },
+ applicationsPage:function (app, trail, tab) {
+ if (trail === undefined) trail = app
+ var that = this
+ this.appTree.fetch({success:function () {
+ var appExplorer = new ExplorerView({
+ collection:that.appTree,
+ appRouter:that
+ })
+ that.showView("#application-content", appExplorer)
+ if (trail !== undefined) appExplorer.show(trail)
+ }})
+ },
+ catalogPage: function (catalogItemKind, id) {
+ var catalogResource = new CatalogView({
+ locations: this.locations,
+ appRouter: this,
+ kind: catalogItemKind,
+ id: id
+ });
+ this.showView("#application-content", catalogResource);
+ },
+ scriptGroovyPage:function () {
+ if (this.scriptGroovyResource === undefined)
+ this.scriptGroovyResource = new ScriptGroovyView({})
+ this.showView("#application-content", this.scriptGroovyResource)
+ $(".nav1").removeClass("active")
+ $(".nav1_script").addClass("active")
+ $(".nav1_script_groovy").addClass("active")
+ },
+ helpPage:function () {
+ $("#application-content").html(_.template(HelpHtml, {}))
+ $(".nav1").removeClass("active")
+ $(".nav1_help").addClass("active")
+ },
+ labsPage:function () {
+ $("#application-content").html(_.template(LabsHtml, {}))
+ $(".nav1").removeClass("active")
+ },
+
+ /** Triggers the Backbone.Router process which drives this GUI through Backbone.history,
+ * after starting background server health checks and waiting for confirmation of health
+ * (or user click-through). */
+ startBrooklynGui: function() {
+ serverStatus.whenUp(function() { Backbone.history.start(); });
+ serverStatus.autoUpdate();
+ _.delay(serverCautionOverlay.warnIfNotLoaded, 2*1000)
+ }
+ });
+
+ $.ajax({
+ type: "GET",
+ url: "/v1/server/user",
+ dataType: "text"
+ }).done(function (data) {
+ if (data != null) {
+ $("#user").html(_.escape(data));
+ }
+ });
+
+ return Router
+})
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/util/brooklyn-utils.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/util/brooklyn-utils.js b/src/main/webapp/assets/js/util/brooklyn-utils.js
new file mode 100644
index 0000000..5f3915c
--- /dev/null
+++ b/src/main/webapp/assets/js/util/brooklyn-utils.js
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ */
+
+/* brooklyn utility methods */
+
+define([
+ 'jquery', 'underscore'
+], function ($, _) {
+
+ var Util = {};
+
+ /**
+ * @return {string} empty string if s is null or undefined, otherwise result of _.escape(s)
+ */
+ Util.escape = function (s) {
+ if (s == undefined || s == null) return "";
+ return _.escape(s);
+ };
+
+ function isWholeNumber(v) {
+ return (Math.abs(Math.round(v) - v) < 0.000000000001);
+ }
+
+ Util.roundIfNumberToNumDecimalPlaces = function (v, mantissa) {
+ if (!_.isNumber(v) || mantissa < 0)
+ return v;
+
+ if (isWholeNumber(v))
+ return Math.round(v);
+
+ var vk = v, xp = 1;
+ for (var i=0; i < mantissa; i++) {
+ vk *= 10;
+ xp *= 10;
+ if (isWholeNumber(vk)) {
+ return Math.round(vk)/xp;
+ }
+ }
+ return Number(v.toFixed(mantissa));
+ };
+
+ Util.toDisplayString = function(data) {
+ if (data==null) return null;
+ data = Util.roundIfNumberToNumDecimalPlaces(data, 4);
+ if (typeof data !== 'string')
+ data = JSON.stringify(data);
+ return Util.escape(data);
+ };
+
+ Util.toTextAreaString = function(data) {
+ if (data==null) return null;
+ data = Util.roundIfNumberToNumDecimalPlaces(data, 8);
+ if (typeof data !== 'string')
+ data = JSON.stringify(data, null, 2);
+ return data;
+ };
+
+ if (!String.prototype.trim) {
+ // some older javascripts do not support 'trim' (including jasmine spec runner) so let's define it
+ String.prototype.trim = function(){
+ return this.replace(/^\s+|\s+$/g, '');
+ };
+ }
+
+ // from http://stackoverflow.com/questions/646628/how-to-check-if-a-string-startswith-another-string
+ if (typeof String.prototype.startsWith != 'function') {
+ String.prototype.startsWith = function (str){
+ return this.slice(0, str.length) == str;
+ };
+ }
+ if (typeof String.prototype.endsWith != 'function') {
+ String.prototype.endsWith = function (str){
+ return this.slice(-str.length) == str;
+ };
+ }
+
+ // poor-man's copy
+ Util.promptCopyToClipboard = function(text) {
+ window.prompt("To copy to the clipboard, press Ctrl+C then Enter.", text);
+ };
+
+ /**
+ * Returns the path component of a string URL. e.g. http://example.com/bob/bob --> /bob/bob
+ */
+ Util.pathOf = function(string) {
+ if (!string) return "";
+ var a = document.createElement("a");
+ a.href = string;
+ return a.pathname;
+ };
+
+ /**
+ * Extracts the value of the given input. Returns true/false for for checkboxes
+ * rather than "on" or "off".
+ */
+ Util.inputValue = function($input) {
+ if ($input.attr("type") === "checkbox") {
+ return $input.is(":checked");
+ } else {
+ return $input.val();
+ }
+ };
+
+ /**
+ * Updates or initialises the given model with the values of named elements under
+ * the given element. Force-updates the model by setting silent: true.
+ */
+ Util.bindModelFromForm = function(modelOrConstructor, $el) {
+ var model = _.isFunction(modelOrConstructor) ? new modelOrConstructor() : modelOrConstructor;
+ var inputs = {};
+
+ // Look up all named elements
+ $("[name]", $el).each(function(idx, inp) {
+ var input = $(inp);
+ var name = input.attr("name");
+ inputs[name] = Util.inputValue(input);
+ });
+ model.set(inputs, { silent: true });
+ return model;
+ };
+
+ /**
+ * Parses xhrResponse.responseText as JSON and returns its message. Returns
+ * alternate message if parsing fails or the parsed object has no message.
+ * @param {jqXHR} xhrResponse
+ * @param {string} alternateMessage
+ * @param {string=} logMessage if false or null, does not log;
+ * otherwise it logs a message and the xhrResponse, with logMessage
+ * (or with alternateMessage if logMessage is true)
+ * @returns {*}
+ */
+ Util.extractError = function (xhrResponse, alternateMessage, logMessage) {
+ if (logMessage) {
+ if (logMessage === true) {
+ console.error(alternateMessage);
+ } else {
+ console.error(logMessage);
+ }
+ console.log(xhrResponse);
+ }
+
+ try {
+ var response = JSON.parse(xhrResponse.responseText);
+ return response.message ? response.message : alternateMessage;
+ } catch (e) {
+ return alternateMessage;
+ }
+ };
+
+ secretWords = [ "password", "passwd", "credential", "secret", "private", "access.cert", "access.key" ];
+
+ Util.isSecret = function (key) {
+ if (!key) return false;
+ key = key.toString().toLowerCase();
+ for (secretWord in secretWords)
+ if (key.indexOf(secretWords[secretWord]) >= 0)
+ return true;
+ return false;
+ };
+
+ Util.logout = function logout() {
+ $.ajax({
+ type: "POST",
+ dataType: "text",
+ url: "/logout",
+ success: function() {
+ window.location.replace("/");
+ },
+ failure: function() {
+ window.location.replace("/");
+ }
+ });
+ }
+
+ Util.setSelectionRange = function (input, selectionStart, selectionEnd) {
+ if (input.setSelectionRange) {
+ input.focus();
+ input.setSelectionRange(selectionStart, selectionEnd);
+ }
+ else if (input.createTextRange) {
+ var range = input.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', selectionEnd);
+ range.moveStart('character', selectionStart);
+ range.select();
+ }
+ };
+
+ Util.setCaretToPos = function (input, pos) {
+ Util.setSelectionRange(input, pos, pos);
+ };
+
+ $.fn.setCaretToStart = function() {
+ this.each(function(index, elem) {
+ Util.setCaretToPos(elem, 0);
+ $(elem).scrollTop(0);
+ });
+ return this;
+ };
+
+ $("#logout-link").on("click", function (e) {
+ e.preventDefault();
+ Util.logout()
+ return false;
+ });
+
+ return Util;
+
+});
+
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/util/brooklyn-view.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/util/brooklyn-view.js b/src/main/webapp/assets/js/util/brooklyn-view.js
new file mode 100644
index 0000000..7151ae1
--- /dev/null
+++ b/src/main/webapp/assets/js/util/brooklyn-view.js
@@ -0,0 +1,352 @@
+/*
+ * 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.
+*/
+
+/* brooklyn extensions for supporting views */
+define([
+ "jquery", "underscore", "backbone", "brooklyn-utils",
+ "text!tpl/lib/basic-modal.html",
+ "text!tpl/lib/config-key-type-value-input-pair.html"
+ ], function (
+ $, _, Backbone, Util,
+ ModalHtml, ConfigKeyInputHtml
+) {
+
+ var module = {};
+
+ module.refresh = true;
+
+ /** Toggles automatic refreshes of instances of View. */
+ module.toggleRefresh = function () {
+ this.refresh = !this.refresh;
+ return this.refresh;
+ };
+
+ // TODO this customising of the View prototype could be expanded to include
+ // other methods from viewutils. see discussion at
+ // https://github.com/brooklyncentral/brooklyn/pull/939
+
+ // add close method to all views for clean-up
+ // (NB we have to update the prototype _here_ before any views are instantiated;
+ // see "close" called below in "showView")
+ Backbone.View.prototype.close = function () {
+ // call user defined close method if exists
+ this.viewIsClosed = true;
+ if (_.isFunction(this.beforeClose)) {
+ this.beforeClose();
+ }
+ _.each(this._periodicFunctions, function (i) {
+ clearInterval(i);
+ });
+ this.remove();
+ this.unbind();
+ };
+
+ Backbone.View.prototype.viewIsClosed = false;
+
+ /**
+ * Registers a callback (cf setInterval) that is unregistered cleanly when the view
+ * closes. The callback is run in the context of the owning view, so callbacks can
+ * refer to 'this' safely.
+ */
+ Backbone.View.prototype.callPeriodically = function (uid, callback, interval) {
+ if (!this._periodicFunctions) {
+ this._periodicFunctions = {};
+ }
+ var old = this._periodicFunctions[uid];
+ if (old) clearInterval(old);
+
+ // Wrap callback in function that checks whether updates are enabled
+ var periodic = function () {
+ if (module.refresh) {
+ callback.apply(this);
+ }
+ };
+ // Bind this to the view
+ periodic = _.bind(periodic, this);
+ this._periodicFunctions[uid] = setInterval(periodic, interval);
+ };
+
+ /**
+ * A form that listens to modifications to its inputs, maintaining a model that is
+ * submitted when a button with class 'submit' is clicked.
+ *
+ * Expects a body view or a template function to render.
+ */
+ module.Form = Backbone.View.extend({
+ events: {
+ "change": "onChange",
+ "submit": "onSubmit"
+ },
+
+ initialize: function() {
+ if (!this.options.body && !this.options.template) {
+ throw new Error("body view or template function required by GenericForm");
+ } else if (!this.options.onSubmit) {
+ throw new Error("onSubmit function required by GenericForm");
+ }
+ this.onSubmitCallback = this.options.onSubmit;
+ this.model = new (this.options.model || Backbone.Model);
+ _.bindAll(this, "onSubmit", "onChange");
+ this.render();
+ },
+
+ beforeClose: function() {
+ if (this.options.body) {
+ this.options.body.close();
+ }
+ },
+
+ render: function() {
+ if (this.options.body) {
+ this.options.body.render();
+ this.$el.html(this.options.body.$el);
+ } else {
+ this.$el.html(this.options.template());
+ }
+ // Initialise the model with existing values
+ Util.bindModelFromForm(this.model, this.$el);
+ return this;
+ },
+
+ onChange: function(e) {
+ var target = $(e.target);
+ var name = target.attr("name");
+ this.model.set(name, Util.inputValue(target), { silent: true });
+ },
+
+ onSubmit: function(e) {
+ e.preventDefault();
+ // Could validate model
+ this.onSubmitCallback(this.model.clone());
+ return false;
+ }
+
+ });
+
+ /**
+ * A view to render another view in a modal. Give another view to render as
+ * the `body' parameter that has an onSubmit function that will be called
+ * when the modal's `Save' button is clicked, and/or an onCancel callback
+ * that will be called when the modal is closed without saving.
+ *
+ * The onSubmit callback should return either:
+ * <ul>
+ * <li><b>nothing</b>: the callback is treated as successful
+ * <li><b>true</b> or <b>false</b>: the callback is treated as appropriate
+ * <li>a <b>promise</b> with `done' and `fail' callbacks (for example a jqXHR object):
+ * The callback is treated as successful when the promise is done without error.
+ * <li><b>anything else</b>: the callback is treated as successful
+ * </ul>
+ * When the onSubmit callback is successful the modal is closed.
+ *
+ * The return value of the onCancel callback is ignored.
+ *
+ * The modal will still be open and visible when the onSubmit callback is called.
+ * The modal will have been closed when the onCancel callback is called.
+ */
+ module.Modal = Backbone.View.extend({
+
+ id: _.uniqueId("modal"),
+ className: "modal",
+ template: _.template(ModalHtml),
+
+ events: {
+ "hide": "onClose",
+ "click .modal-submit": "onSubmit"
+ },
+
+ initialize: function() {
+ if (!this.options.body) {
+ throw new Error("Modal view requires body to render");
+ }
+ _.bindAll(this, "onSubmit", "onCancel", "show");
+ if (this.options.autoOpen) {
+ this.show();
+ }
+ },
+
+ beforeClose: function() {
+ if (this.options.body) {
+ this.options.body.close();
+ }
+ },
+
+ render: function() {
+ var optionalTitle = this.options.body.title;
+ var title = _.isFunction(optionalTitle)
+ ? optionalTitle()
+ : _.isString(optionalTitle)
+ ? optionalTitle : this.options.title;
+ this.$el.html(this.template({
+ title: title,
+ submitButtonText: this.options.submitButtonText || "Apply",
+ cancelButtonText: this.options.cancelButtonText || "Cancel"
+ }));
+ this.options.body.render();
+ this.$(".modal-body").html(this.options.body.$el);
+ return this;
+ },
+
+ show: function() {
+ this.render().$el.modal();
+ return this;
+ },
+
+ onSubmit: function(event) {
+ if (_.isFunction(this.options.body.onSubmit)) {
+ var submission = this.options.body.onSubmit.apply(this.options.body, [event]);
+ var self = this;
+ var submissionSuccess = function() {
+ // Closes view via event.
+ self.closingSuccessfully = true;
+ self.$el.modal("hide");
+ };
+ var submissionFailure = function () {
+ // Better response.
+ console.log("modal submission failed!", arguments);
+ };
+ // Assuming no value is fine
+ if (!submission) {
+ submission = true;
+ }
+ if (_.isBoolean(submission) && submission) {
+ submissionSuccess();
+ } else if (_.isBoolean(submission)) {
+ submissionFailure();
+ } else if (_.isFunction(submission.done) && _.isFunction(submission.fail)) {
+ submission.done(submissionSuccess).fail(submissionFailure);
+ } else {
+ // assuming success and closing modal
+ submissionSuccess()
+ }
+ }
+ return false;
+ },
+
+ onCancel: function () {
+ if (_.isFunction(this.options.body.onCancel)) {
+ this.options.body.onCancel.apply(this.options.body);
+ }
+ },
+
+ onClose: function () {
+ if (!this.closingSuccessfully) {
+ this.onCancel();
+ }
+ this.close();
+ }
+ });
+
+ /**
+ * Shows a modal with yes/no buttons as a user confirmation prompt.
+ * @param {string} question The message to show in the body of the modal
+ * @param {string} [title] An optional title to show. Uses generic default if not given.
+ * @returns {jquery.Deferred} The promise from a jquery.Deferred object. The
+ * promise is resolved if the modal was submitted normally and rejected
+ * otherwise.
+ */
+ module.requestConfirmation = function (question, title) {
+ var deferred = $.Deferred();
+ var Confirmation = Backbone.View.extend({
+ title: title || "Confirm action",
+ render: function () {
+ this.$el.html(question || "");
+ },
+ onSubmit: function () {
+ deferred.resolve();
+ },
+ onCancel: function () {
+ deferred.reject();
+ }
+ });
+ new module.Modal({
+ body: new Confirmation(),
+ autoOpen: true,
+ submitButtonText: "Yes",
+ cancelButtonText: "No"
+ });
+ return deferred.promise();
+ };
+
+ /** Creates, displays and returns a modal with the given view used as its body */
+ module.showModalWith = function (bodyView) {
+ return new module.Modal({body: bodyView}).show();
+ };
+
+ /**
+ * Presents inputs for config key names/values with buttons to add/remove entries
+ * and a function to extract a map of name->value.
+ */
+ module.ConfigKeyInputPairList = Backbone.View.extend({
+ template: _.template(ConfigKeyInputHtml),
+ // Could listen to input change events and add 'error' class to any type inputs
+ // that duplicate values.
+ events: {
+ "click .config-key-row-remove": "rowRemove",
+ "keypress .last": "rowAdd"
+ },
+ render: function () {
+ if (this.options.configKeys) {
+ var templated = _.map(this.options.configKeys, function (value, key) {
+ return this.templateRow(key, value);
+ }, this);
+ this.$el.html(templated.join(""));
+ }
+ this.$el.append(this.templateRow());
+ this.markLast();
+ return this;
+ },
+ rowAdd: function (event) {
+ this.$el.append(this.templateRow());
+ this.markLast();
+ },
+ rowRemove: function (event) {
+ $(event.currentTarget).parent().remove();
+ if (this.$el.children().length == 0) {
+ this.rowAdd();
+ }
+ this.markLast();
+ },
+ markLast: function () {
+ this.$(".last").removeClass("last");
+ // Marking inputs rather than parent div to avoid weird behaviour when
+ // remove row button is triggered with the keyboard.
+ this.$(".config-key-type").last().addClass("last");
+ this.$(".config-key-value").last().addClass("last");
+ },
+ templateRow: function (type, value) {
+ return this.template({type: type || "", value: value || ""});
+ },
+ getConfigKeys: function () {
+ var cks = {};
+ this.$(".config-key-type").each(function (index, input) {
+ input = $(input);
+ var type = input.val() && input.val().trim();
+ var value = input.next().val() && input.next().val().trim();
+ if (type && value) {
+ cks[type] = value;
+ }
+ });
+ return cks;
+ }
+ });
+
+ return module;
+
+});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/util/brooklyn.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/util/brooklyn.js b/src/main/webapp/assets/js/util/brooklyn.js
new file mode 100644
index 0000000..702b59b
--- /dev/null
+++ b/src/main/webapp/assets/js/util/brooklyn.js
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+
+/** brooklyn extension to make console methods available and simplify access to other utils */
+
+define([
+ "underscore", "brooklyn-view", "brooklyn-utils"
+], function (_, BrooklynViews, BrooklynUtils) {
+
+ /**
+ * Makes the console API safe to use:
+ * - Stubs missing methods to prevent errors when no console is present.
+ * - Exposes a global `log` function that preserves line numbering and formatting.
+ *
+ * Idea from https://gist.github.com/bgrins/5108712
+ */
+ (function () {
+ var noop = function () {},
+ consoleMethods = [
+ 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
+ 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
+ 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
+ 'timeStamp', 'trace', 'warn'
+ ],
+ length = consoleMethods.length,
+ console = (window.console = window.console || {});
+
+ while (length--) {
+ var method = consoleMethods[length];
+
+ // Only stub undefined methods.
+ if (!console[method]) {
+ console[method] = noop;
+ }
+ }
+
+ if (Function.prototype.bind) {
+ window.log = Function.prototype.bind.call(console.log, console);
+ } else {
+ window.log = function () {
+ Function.prototype.apply.call(console.log, console, arguments);
+ };
+ }
+ })();
+
+ var template = _.template;
+ _.mixin({
+ /**
+ * @param {string} text
+ * @return string The text with HTML comments removed.
+ */
+ stripComments: function (text) {
+ return text.replace(/<!--(.|[\n\r\t])*?-->\r?\n?/g, "");
+ },
+ /**
+ * As the real _.template, calling stripComments on text.
+ */
+ template: function (text, data, settings) {
+ return template(_.stripComments(text), data, settings);
+ }
+ });
+
+ var Brooklyn = {
+ view: BrooklynViews,
+ util: BrooklynUtils
+ };
+
+ return Brooklyn;
+});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/util/dataTables.extensions.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/util/dataTables.extensions.js b/src/main/webapp/assets/js/util/dataTables.extensions.js
new file mode 100644
index 0000000..74a548e
--- /dev/null
+++ b/src/main/webapp/assets/js/util/dataTables.extensions.js
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ * ---
+ *
+ * This code has been created by the Apache Brooklyn contributors.
+ * It is heavily based on earlier software but rewritten for clarity
+ * and to preserve license integrity.
+ *
+ * This work is based on the existing jQuery DataTables plug-ins for:
+ *
+ * * fnStandingRedraw by Jonathan Hoguet,
+ * http://www.datatables.net/plug-ins/api/fnStandingRedraw
+ *
+ * * fnProcessingIndicator by Allan Chappell
+ * https://www.datatables.net/plug-ins/api/fnProcessingIndicator
+ *
+ */
+define([
+ "jquery", "jquery-datatables"
+], function($, dataTables) {
+
+$.fn.dataTableExt.oApi.fnStandingRedraw = function(oSettings) {
+ if (oSettings.oFeatures.bServerSide === false) {
+ // remember and restore cursor position
+ var oldDisplayStart = oSettings._iDisplayStart;
+ oSettings.oApi._fnReDraw(oSettings);
+ oSettings._iDisplayStart = oldDisplayStart;
+ oSettings.oApi._fnCalculateEnd(oSettings);
+ }
+ // and force draw
+ oSettings.oApi._fnDraw(oSettings);
+};
+
+
+jQuery.fn.dataTableExt.oApi.fnProcessingIndicator = function(oSettings, bShow) {
+ if (typeof bShow === "undefined") bShow=true;
+ this.oApi._fnProcessingDisplay(oSettings, bShow);
+};
+
+});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/util/jquery.slideto.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/util/jquery.slideto.js b/src/main/webapp/assets/js/util/jquery.slideto.js
new file mode 100644
index 0000000..17afeed
--- /dev/null
+++ b/src/main/webapp/assets/js/util/jquery.slideto.js
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ *
+ * ---
+ *
+ * This code has been created by the Apache Brooklyn contributors.
+ * It is heavily based on earlier software but rewritten for readability
+ * and to preserve license integrity.
+ *
+ * Our influences are:
+ *
+ * * jquery.slideto.min.js in Swagger UI, provenance unknown, added in:
+ * https://github.com/wordnik/swagger-ui/commit/d2eb882e5262e135dfa3f5919796bbc3785880b8#diff-bd86720650a2ebd1ab11e870dc475564
+ *
+ * Swagger UI is distributed under ASL but it is not clear that this code originated in that project.
+ * No other original author could be identified.
+ *
+ * * Nearly identical code referenced here:
+ * http://stackoverflow.com/questions/12375440/scrolling-works-in-chrome-but-not-in-firefox-or-ie
+ *
+ * Note that the project https://github.com/Sleavely/jQuery-slideto is NOT this.
+ *
+ */
+(function(jquery){
+jquery.fn.slideto=function(opts) {
+ opts = _.extend( {
+ highlight: true,
+ slide_duration: "slow",
+ highlight_duration: 3000,
+ highlight_color: "#FFFF99" },
+ opts);
+ return this.each(function() {
+ $target=jquery(this);
+ jquery("body").animate(
+ { scrollTop: $target.offset().top },
+ opts.slide_duration,
+ function() {
+ opts.highlight &&
+ jquery.ui.version &&
+ $target.effect(
+ "highlight",
+ { color: opts.highlight_color },
+ opts.highlight_duration)
+ })
+ });
+}}) (jQuery);
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/src/main/webapp/assets/js/view/activity-details.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/activity-details.js b/src/main/webapp/assets/js/view/activity-details.js
new file mode 100644
index 0000000..fa8b552
--- /dev/null
+++ b/src/main/webapp/assets/js/view/activity-details.js
@@ -0,0 +1,426 @@
+/*
+ * 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 details on an activity/task
+ */
+define([
+ "underscore", "jquery", "backbone", "brooklyn-utils", "view/viewutils", "moment",
+ "model/task-summary",
+ "text!tpl/apps/activity-details.html", "text!tpl/apps/activity-table.html",
+
+ "bootstrap", "jquery-datatables", "datatables-extensions"
+], function (_, $, Backbone, Util, ViewUtils, moment,
+ TaskSummary,
+ ActivityDetailsHtml, ActivityTableHtml) {
+
+ var activityTableTemplate = _.template(ActivityTableHtml),
+ activityDetailsTemplate = _.template(ActivityDetailsHtml);
+
+ function makeActivityTable($el) {
+ $el.html(_.template(ActivityTableHtml));
+ var $subTable = $('.activity-table', $el);
+ $subTable.attr('width', 569-6-6 /* subtract padding */)
+
+ return ViewUtils.myDataTable($subTable, {
+ "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
+ $(nRow).attr('id', aData[0])
+ $(nRow).addClass('activity-row')
+ },
+ "aoColumnDefs": [ {
+ "mRender": function ( data, type, row ) { return Util.escape(data) },
+ "aTargets": [ 1, 2, 3 ]
+ }, {
+ "bVisible": false,
+ "aTargets": [ 0 ]
+ } ],
+ "aaSorting":[] // default not sorted (server-side order)
+ });
+ }
+
+ var ActivityDetailsView = Backbone.View.extend({
+ template: activityDetailsTemplate,
+ 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: [],
+ firstLoad: true,
+ events:{
+ "click #activities-children-table .activity-table tr":"childrenRowClick",
+ "click #activities-submitted-table .activity-table tr":"submittedRowClick",
+ 'click .showDrillDownSubmittedByAnchor':'showDrillDownSubmittedByAnchor',
+ 'click .showDrillDownBlockerOfAnchor':'showDrillDownBlockerOfAnchor',
+ 'click .backDrillDown':'backDrillDown'
+ },
+ // requires taskLink or task; breadcrumbs is optional
+ initialize:function () {
+ var that = this;
+ this.taskLink = this.options.taskLink;
+ this.taskId = this.options.taskId;
+ if (this.options.task)
+ this.task = this.options.task;
+ else if (this.options.tabView)
+ this.task = this.options.tabView.collection.get(this.taskId);
+ if (!this.taskLink && this.task) this.taskLink = this.task.get('links').self;
+ if (!this.taskLink && this.taskId) this.taskLink = "v1/activities/"+this.taskId;;
+
+ this.tabView = this.options.tabView || null;
+
+ if (this.options.breadcrumbs) this.breadcrumbs = this.options.breadcrumbs;
+
+ this.$el.html(this.template({ taskLink: this.taskLink, taskId: this.taskId, task: this.task, breadcrumbs: this.breadcrumbs }));
+ this.$el.addClass('activity-detail-panel');
+
+ this.childrenTable = makeActivityTable(this.$('#activities-children-table'));
+ this.subtasksTable = makeActivityTable(this.$('#activities-submitted-table'));
+
+ ViewUtils.attachToggler(this.$el)
+
+ if (this.task) {
+ this.renderTask()
+ this.setUpPolling()
+ } else {
+ ViewUtils.fadeToIndicateInitialLoad(this.$el);
+ this.$el.css('cursor', 'wait')
+ $.get(this.taskLink, function(data) {
+ ViewUtils.cancelFadeOnceLoaded(that.$el);
+ that.task = new TaskSummary.Model(data)
+ that.renderTask()
+ that.setUpPolling();
+ }).fail(function() { log("unable to load "+that.taskLink) })
+ }
+
+ // initial subtasks may be available from parent, so try to render those
+ // (reliable polling for subtasks, and for children, is set up in setUpPolling )
+ this.renderSubtasks()
+ },
+
+ refreshNow: function(initial) {
+ var that = this
+ $.get(this.taskLink, function(data) {
+ that.task = new TaskSummary.Model(data)
+ that.renderTask()
+ if (initial) that.setUpPolling();
+ })
+ },
+ renderTask: function() {
+ // update task fields
+ var that = this, firstLoad = this.firstLoad;
+ this.firstLoad = false;
+
+ if (firstLoad && this.task) {
+// log("rendering "+firstLoad+" "+this.task.get('isError')+" "+this.task.id);
+ if (this.task.get('isError')) {
+ // on first load, expand the details if there is a problem
+ var $details = this.$(".toggler-region.task-detail .toggler-header");
+ ViewUtils.showTogglerClickElement($details);
+ }
+ }
+
+ this.updateFields('displayName', 'entityDisplayName', 'id', 'description', 'currentStatus', 'blockingDetails');
+ this.updateFieldWith('blockingTask',
+ function(v) {
+ return "<a class='showDrillDownBlockerOfAnchor handy' link='"+_.escape(v.link)+"' id='"+v.metadata.id+"'>"+
+ that.displayTextForLinkedTask(v)+"</a>" })
+ this.updateFieldWith('result',
+ function(v) {
+ // use display string (JSON.stringify(_.escape(v)) because otherwise list of [null,null] is just ","
+ var vs = Util.toDisplayString(v);
+ if (vs.trim().length==0) {
+ return " (empty result)";
+ } else if (vs.length<20 && !/\r|\n/.exec(v)) {
+ return " with result: <span class='result-literal'>"+vs+"</span>";
+ } else {
+ return "<div class='result-literal'>"+vs.replace(/\n+/g,"<br>")+"</div>"
+ }
+ })
+ this.updateFieldWith('tags', function(tags) {
+ var tagBody = "";
+ for (var tag in tags) {
+ tagBody += "<div class='activity-tag-giftlabel'>"+Util.toDisplayString(tags[tag])+"</div>";
+ }
+ return tagBody;
+ })
+
+ var submitTimeUtc = this.updateFieldWith('submitTimeUtc',
+ function(v) { return v <= 0 ? "-" : moment(v).format('D MMM YYYY H:mm:ss.SSS')+" <i>"+moment(v).fromNow()+"</i>" })
+ var startTimeUtc = this.updateFieldWith('startTimeUtc',
+ function(v) { return v <= 0 ? "-" : moment(v).format('D MMM YYYY H:mm:ss.SSS')+" <i>"+moment(v).fromNow()+"</i>" })
+ this.updateFieldWith('endTimeUtc',
+ function(v) { return v <= 0 ? "-" : moment(v).format('D MMM YYYY H:mm:ss.SSS')+" <i>"+moment(v).from(startTimeUtc, true)+" later</i>" })
+
+ ViewUtils.updateTextareaWithData(this.$(".task-json .for-textarea"),
+ Util.toTextAreaString(this.task), false, false, 150, 400)
+
+ ViewUtils.updateTextareaWithData(this.$(".task-detail .for-textarea"),
+ this.task.get('detailedStatus'), false, false, 30, 250)
+
+ this.updateFieldWith('streams',
+ function(streams) {
+ // Stream names presented alphabetically
+ var keys = _.keys(streams);
+ keys.sort();
+ var result = "";
+ for (var i = 0; i < keys.length; i++) {
+ var name = keys[i];
+ var stream = streams[name];
+ result += "<div class='activity-stream-div'>" +
+ "<span class='activity-label'>" +
+ _.escape(name) +
+ "</span><span>" +
+ "<a href='" + stream.link + "'>download</a>" +
+ (stream.metadata["sizeText"] ? " (" + _.escape(stream.metadata["sizeText"]) + ")" : "") +
+ "</span></div>";
+ }
+ return result;
+ });
+
+ this.updateFieldWith('submittedByTask',
+ function(v) { return "<a class='showDrillDownSubmittedByAnchor handy' link='"+_.escape(v.link)+"' id='"+v.metadata.id+"'>"+
+ that.displayTextForLinkedTask(v)+"</a>" })
+
+ if (this.task.get("children").length==0)
+ this.$('.toggler-region.tasks-children').hide();
+ },
+ setUpPolling: function() {
+ var that = this
+
+ // on first load, clear any funny cursor
+ this.$el.css('cursor', 'auto')
+
+ this.task.url = this.taskLink;
+ this.task.on("all", this.renderTask, this)
+
+ ViewUtils.get(this, this.taskLink, function(data) {
+ // if we can get the data, then start fetching certain things repeatedly
+ // (would be good to skip the immediate "doitnow" below but not a big deal)
+ ViewUtils.fetchRepeatedlyWithDelay(that, that.task, { doitnow: true });
+
+ // and set up to load children (now that the task is guaranteed to be loaded)
+ that.children = new TaskSummary.Collection()
+ that.children.url = that.task.get("links").children
+ that.children.on("reset", that.renderChildren, that)
+ ViewUtils.fetchRepeatedlyWithDelay(that, that.children, {
+ fetchOptions: { reset: true }, doitnow: true, fadeTarget: that.$('.tasks-children') });
+ }).fail( function() { that.$('.toggler-region.tasks-children').hide() } );
+
+
+ $.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", that.renderSubtasks, that)
+ ViewUtils.fetchRepeatedlyWithDelay(that, that.collection, {
+ fetchOptions: { reset: true }, doitnow: true, fadeTarget: that.$('.tasks-submitted') });
+ } else {
+ that.collection.on("reset", that.renderSubtasks, that)
+ 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") && task.get("entityId")!=that.task.get("entityId") ? task.get("entityDisplayName") + ": " : "") +
+ task.get("displayName"),
+ task.get("submitTimeUtc") <= 0 ? "-" : moment(task.get("submitTimeUtc")).calendar(),
+ task.get("currentStatus")
+ ];
+ });
+ if (children && children.length>0) {
+ this.$('.toggler-region.tasks-children').show();
+ } else {
+ this.$('.toggler-region.tasks-children').hide();
+ }
+ },
+ renderSubtasks: function() {
+ var that = this
+ var taskId = this.taskId || (this.task ? this.task.id : null);
+ if (!this.collection) {
+ this.$('.toggler-region.tasks-submitted').hide();
+ return;
+ }
+ if (!taskId) {
+ // task not available yet; just wait for it to be loaded
+ // (and in worst case, if it can't be loaded, this panel stays faded)
+ 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 (var taskI in this.collection.models) {
+ var task = this.collection.models[taskI]
+ var submittedBy = task.get("submittedByTask")
+ if (submittedBy!=null && submittedBy.metadata!=null && submittedBy.metadata["id"] == taskId &&
+ (!this.children || 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 || task.get("entityId")!=that.task.get("entityId")) ? task.get("entityDisplayName") + ": " : "") +
+ task.get("displayName"),
+ task.get("submitTimeUtc") <= 0 ? "-" : moment(task.get("submitTimeUtc")).calendar(),
+ task.get("currentStatus")
+ ];
+ });
+ if (subtasks && subtasks.length>0) {
+ this.$('.toggler-region.tasks-submitted').show();
+ } else {
+ this.$('.toggler-region.tasks-submitted').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, _.escape)
+ },
+ updateFields: function() {
+ _.map(arguments, this.updateField, this);
+ },
+ updateFieldWith: function(field, f) {
+ var v = this.task.get(field)
+ if (v !== undefined && v != null &&
+ (typeof v !== "object" || _.size(v) > 0)) {
+ this.$('.updateField-'+field, this.$el).html( f(v) );
+ this.$('.ifField-'+field, this.$el).show();
+ } else {
+ // blank if there is no value
+ this.$('.updateField-'+field).empty();
+ this.$('.ifField-'+field).hide();
+ }
+ return v
+ },
+ childrenRowClick:function(evt) {
+ var row = $(evt.currentTarget).closest("tr");
+ var id = row.attr("id");
+ this.showDrillDownTask("subtask of", this.children.get(id).get("links").self, id, this.children.get(id))
+ },
+ submittedRowClick:function(evt) {
+ var row = $(evt.currentTarget).closest("tr");
+ 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, id)
+ },
+
+ showDrillDownSubmittedByAnchor: function(from) {
+ var $a = $(from.target).closest('a');
+ this.showDrillDownTask("submitter of", $a.attr("link"), $a.attr("id"))
+ },
+ showDrillDownBlockerOfAnchor: function(from) {
+ var $a = $(from.target).closest('a');
+ this.showDrillDownTask("blocker of", $a.attr("link"), $a.attr("id"))
+ },
+ showDrillDownTask: function(relation, newTaskLink, newTaskId, newTask) {
+// log("activities deeper drill down - "+newTaskId +" / "+newTaskLink)
+ var that = this;
+
+ var newBreadcrumbs = [ relation + ' ' +
+ this.task.get('entityDisplayName') + ' ' +
+ this.task.get('displayName') ].concat(this.breadcrumbs)
+
+ var activityDetailsPanel = new ActivityDetailsView({
+ taskLink: newTaskLink,
+ taskId: newTaskId,
+ task: newTask,
+ tabView: that.tabView,
+ collection: this.collection,
+ breadcrumbs: newBreadcrumbs
+ });
+ activityDetailsPanel.addToView(this.$el);
+ },
+ addToView: function(parent) {
+ if (this.parent) {
+ log("WARN: adding details to view when already added")
+ this.parent = parent;
+ }
+
+ if (Backbone.history && (!this.tabView || !this.tabView.openingQueuedTasks)) {
+ Backbone.history.navigate(Backbone.history.fragment+"/"+"subtask"+"/"+this.taskId);
+ }
+
+ var $t = parent.closest('.slide-panel');
+ var $t2 = $t.after('<div>').next();
+ $t2.addClass('slide-panel');
+
+ // load the drill-down page
+ $t2.html(this.render().el)
+
+ var speed = (!this.tabView || !this.tabView.openingQueuedTasks) ? 300 : 0;
+ $t.animate({
+ left: -600
+ }, speed, function() {
+ $t.hide()
+ });
+
+ $t2.show().css({
+ left: 600
+ , top: 0
+ }).animate({
+ left: 0
+ }, speed);
+ },
+ backDrillDown: function(event) {
+// log("activities drill back from "+this.taskLink)
+ var that = this
+ var $t2 = this.$el.closest('.slide-panel')
+ var $t = $t2.prev()
+
+ if (Backbone.history) {
+ var fragment = Backbone.history.fragment
+ var thisLoc = fragment.indexOf("/subtask/"+this.taskId);
+ if (thisLoc>=0)
+ Backbone.history.navigate( fragment.substring(0, thisLoc) );
+ }
+
+ $t2.animate({
+ left: 569 //prevTable.width()
+ }, 300, function() {
+ that.$el.empty()
+ $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/18b073a9/src/main/webapp/assets/js/view/add-child-invoke.js
----------------------------------------------------------------------
diff --git a/src/main/webapp/assets/js/view/add-child-invoke.js b/src/main/webapp/assets/js/view/add-child-invoke.js
new file mode 100644
index 0000000..1105afe
--- /dev/null
+++ b/src/main/webapp/assets/js/view/add-child-invoke.js
@@ -0,0 +1,61 @@
+/*
+ * 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 as a modal
+ */
+define([
+ "underscore", "jquery", "backbone", "brooklyn", "brooklyn-utils", "view/viewutils",
+ "text!tpl/apps/add-child-modal.html"
+], function(_, $, Backbone, Brooklyn, Util, ViewUtils,
+ AddChildModalHtml) {
+ return Backbone.View.extend({
+ template: _.template(AddChildModalHtml),
+ initialize: function() {
+ this.title = "Add Child to "+this.options.entity.get('name');
+ },
+ render: function() {
+ this.$el.html(this.template(this.options.entity.attributes));
+ return this;
+ },
+ onSubmit: function (event) {
+ var self = this;
+ var childSpec = this.$("#child-spec").val();
+ var start = this.$("#child-autostart").is(":checked");
+ var url = this.options.entity.get('links').children + (!start ? "?start=false" : "");
+ var ajax = $.ajax({
+ type: "POST",
+ url: url,
+ data: childSpec,
+ contentType: "application/yaml",
+ success: function() {
+ self.options.target.reload();
+ },
+ error: function(response) {
+ self.showError(Util.extractError(response, "Error contacting server", url));
+ }
+ });
+ return ajax;
+ },
+ showError: function (message) {
+ this.$(".child-add-error-container").removeClass("hide");
+ this.$(".child-add-error-message").html(message);
+ }
+
+ });
+});