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:52 UTC
[34/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/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js
----------------------------------------------------------------------
diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js b/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js
deleted file mode 100644
index 2c4f012..0000000
--- a/brooklyn-ui/src/main/webapp/assets/js/view/application-add-wizard.js
+++ /dev/null
@@ -1,838 +0,0 @@
-/*
- * 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.
-*/
-/**
- * Builds a Twitter Bootstrap modal as the framework for a Wizard.
- * Also creates an empty Application model.
- */
-define([
- "underscore", "jquery", "backbone", "brooklyn-utils", "js-yaml",
- "model/entity", "model/application", "model/location", "model/catalog-application",
- "text!tpl/app-add-wizard/modal-wizard.html",
- "text!tpl/app-add-wizard/create.html",
- "text!tpl/app-add-wizard/create-step-template-entry.html",
- "text!tpl/app-add-wizard/create-entity-entry.html",
- "text!tpl/app-add-wizard/required-config-entry.html",
- "text!tpl/app-add-wizard/edit-config-entry.html",
- "text!tpl/app-add-wizard/deploy.html",
- "text!tpl/app-add-wizard/deploy-version-option.html",
- "text!tpl/app-add-wizard/deploy-location-row.html",
- "text!tpl/app-add-wizard/deploy-location-option.html",
- "bootstrap"
-
-], function (_, $, Backbone, Util, JsYaml, Entity, Application, Location, CatalogApplication,
- ModalHtml, CreateHtml, CreateStepTemplateEntryHtml, CreateEntityEntryHtml,
- RequiredConfigEntryHtml, EditConfigEntryHtml, DeployHtml,
- DeployVersionOptionHtml, DeployLocationRowHtml, DeployLocationOptionHtml
-) {
-
- /** Special ID to indicate that no locations will be provided when starting the server. */
- var NO_LOCATION_INDICATOR = "__NONE__";
-
- function setVisibility(obj, isVisible) {
- if (isVisible) obj.show();
- else obj.hide();
- }
-
- function setEnablement(obj, isEnabled) {
- obj.attr("disabled", !isEnabled)
- }
-
- /** converts old-style spec with "entities" to camp-style spec with services */
- function oldSpecToCamp(spec) {
- var services;
- if (spec.type) {
- services = [entityToCamp({type: spec.type, version: spec.version, config: spec.config})];
- } else if (spec.entities) {
- services = [];
- var entities = spec.entities;
- for (var i = 0; i < entities.length; i++) {
- services.push(entityToCamp(entities[i]));
- }
- }
- var result = {};
- if (spec.name) result.name = spec.name;
- if (spec.locations) {
- if (spec.locations.length>1)
- result.locations = spec.locations;
- else
- result.location = spec.locations[0];
- }
- if (services) result.services = services;
- // NB: currently nothing else is supported in this spec
- return result;
- }
- function entityToCamp(entity) {
- var result = {};
- if (entity.name && (!options || !options.exclude_name)) result.name = entity.name;
- if (entity.type) result.type = entity.type;
- if (entity.type && entity.version) result.type += ":" + entity.version;
- if (entity.config && _.size(entity.config)) result["brooklyn.config"] = entity.config;
- return result;
- }
- function getConvertedConfigValue(value) {
- try {
- return $.parseJSON(value);
- } catch (e) {
- return value;
- }
- }
-
- var ModalWizard = Backbone.View.extend({
- tagName:'div',
- className:'modal hide fade',
- events:{
- 'click #prev_step':'prevStep',
- 'click #next_step':'nextStep',
- 'click #preview_step':'previewStep',
- 'click #finish_step':'finishStep'
- },
- template:_.template(ModalHtml),
- initialize:function () {
- this.catalog = {}
- this.catalog.applications = {}
- this.model = {}
- this.model.spec = new Application.Spec;
- this.model.yaml = "";
- this.model.mode = "template"; // or "yaml" or "other"
- this.currentStep = 0;
- this.steps = [
- {
- step_id:'what-app',
- title:'Create Application',
- instructions:'Choose or build the application to deploy',
- view:new ModalWizard.StepCreate({ model:this.model, wizard: this, catalog: this.catalog })
- },
- {
- // TODO rather than make this another step -- since we now on preview revert to the yaml tab
- // this should probably be shown in the catalog tab, replacing the other contents.
- step_id:'name-and-locations',
- title:'<%= appName %>',
- instructions:'Specify the locations to deploy to and any additional configuration',
- view:new ModalWizard.StepDeploy({ model:this.model, catalog: this.catalog })
- }
- ]
- },
- beforeClose:function () {
- // ensure we close the sub-views
- _.each(this.steps, function (step) {
- step.view.close()
- }, this)
- },
- render:function () {
- this.$el.html(this.template({}))
- this.renderCurrentStep()
- return this
- },
-
- renderCurrentStep:function (callback) {
- var name = this.model.name || "";
- this.title = this.$("h3#step_title")
- this.instructions = this.$("p#step_instructions")
-
- var currentStepObj = this.steps[this.currentStep]
- this.title.html(_.template(currentStepObj.title)({appName: name}));
- this.instructions.html(currentStepObj.instructions)
- this.currentView = currentStepObj.view
-
- // delegate to sub-views !!
- this.currentView.render()
- this.currentView.updateForState()
- this.$(".modal-body").replaceWith(this.currentView.el)
- if (callback) callback(this.currentView);
-
- this.updateButtonVisibility();
- },
- updateButtonVisibility:function () {
- var currentStepObj = this.steps[this.currentStep]
-
- setVisibility(this.$("#prev_step"), (this.currentStep > 0))
-
- // next shown for first step, but not for yaml
- var nextVisible = (this.currentStep < 1) && (this.model.mode != "yaml")
- setVisibility(this.$("#next_step"), nextVisible)
-
- // previous shown for step 2 (but again, not yaml)
- var previewVisible = (this.currentStep == 1) && (this.model.mode != "yaml")
- setVisibility(this.$("#preview_step"), previewVisible)
-
- // now set next/preview enablement
- if (nextVisible || previewVisible) {
- var nextEnabled = true;
- if (this.currentStep==0 && this.model.mode=="template" && currentStepObj && currentStepObj.view) {
- // disable if this is template selction (lozenge) view, and nothing is selected
- if (! currentStepObj.view.selectedTemplate)
- nextEnabled = false;
- }
-
- if (nextVisible)
- setEnablement(this.$("#next_step"), nextEnabled)
- if (previewVisible)
- setEnablement(this.$("#preview_step"), nextEnabled)
- }
-
- // finish from config step, preview step, and from first step if yaml tab selected (and valid)
- var finishVisible = (this.currentStep >= 1)
- var finishEnabled = finishVisible
- if (!finishEnabled && this.currentStep==0) {
- if (this.model.mode == "yaml") {
- // should do better validation than non-empty
- finishVisible = true;
- var yaml_code = this.$("#yaml_code").val()
- if (yaml_code) {
- finishEnabled = true;
- }
- }
- }
- setVisibility(this.$("#finish_step"), finishVisible)
- setEnablement(this.$("#finish_step"), finishEnabled)
- },
-
- submitApplication:function (event) {
- var that = this
- var $modal = $('.add-app #modal-container .modal')
- $modal.fadeTo(500,0.5);
-
- var yaml;
- if (this.model.mode == "yaml") {
- yaml = this.model.yaml;
- } else {
- // Drop any "None" locations.
- this.model.spec.pruneLocations();
- yaml = JsYaml.safeDump(oldSpecToCamp(this.model.spec.toJSON()));
- }
-
- $.ajax({
- url:'/v1/applications',
- type:'post',
- contentType:'application/yaml',
- processData:false,
- data:yaml,
- success:function (data) {
- that.onSubmissionComplete(true, data, $modal)
- },
- error:function (data) {
- that.onSubmissionComplete(false, data, $modal)
- }
- });
-
- return false
- },
- onSubmissionComplete: function(succeeded, data, $modal) {
- var that = this;
- if (succeeded) {
- $modal.modal('hide')
- $modal.fadeTo(500,1);
- if (that.options.callback) that.options.callback();
- } else {
- log("ERROR submitting application: "+data.responseText);
- var response, summary="Server responded with an error";
- try {
- if (data.responseText) {
- response = JSON.parse(data.responseText)
- if (response) {
- summary = response.message;
- }
- }
- } catch (e) {
- summary = data.responseText;
- }
- that.$el.fadeTo(100,1).delay(200).fadeTo(200,0.2).delay(200).fadeTo(200,1);
- that.steps[that.currentStep].view.showFailure(summary)
- }
- },
-
- prevStep:function () {
- this.currentStep -= 1;
- this.renderCurrentStep();
- },
- nextStep:function () {
- if (this.currentStep == 0) {
- if (this.currentView.validate()) {
- var yaml = (this.currentView && this.currentView.selectedTemplate && this.currentView.selectedTemplate.yaml);
- if (yaml) {
- try {
- yaml = JsYaml.safeLoad(yaml);
- hasLocation = yaml.location || yaml.locations;
- if (!hasLocation) {
- // look for locations defined in locations
- svcs = yaml.services;
- if (svcs) {
- for (svcI in svcs) {
- if (svcs[svcI].location || svcs[svcI].locations) {
- hasLocation = true;
- break;
- }
- }
- }
- }
- yaml = (hasLocation ? true : false);
- } catch (e) {
- log("Warning: could not parse yaml template")
- log(yaml);
- yaml = false;
- }
- }
- if (yaml) {
- // it's a yaml catalog template which includes a location, show the yaml tab
- $("ul#app-add-wizard-create-tab").find("a[href='#yamlTab']").tab('show');
- $("#yaml_code").setCaretToStart();
- } else {
- // it's a java catalog template or yaml template without a location, go to wizard
- this.currentStep += 1;
- this.renderCurrentStep();
- }
- } else {
- // the call to validate will have done the showFailure
- }
- } else {
- throw "Unexpected step: "+this.currentStep;
- }
- },
- previewStep:function () {
- if (this.currentView.validate()) {
- this.currentStep = 0;
- var that = this;
- this.renderCurrentStep(function callback(view) {
- // Drop any "None" locations.
- that.model.spec.pruneLocations();
- $("textarea#yaml_code").val(JsYaml.safeDump(oldSpecToCamp(that.model.spec.toJSON())));
- $("ul#app-add-wizard-create-tab").find("a[href='#yamlTab']").tab('show');
- $("#yaml_code").setCaretToStart();
- });
- } else {
- // call to validate should showFailure
- }
- },
- finishStep:function () {
- if (this.currentView.validate()) {
- this.submitApplication()
- } else {
- // call to validate should showFailure
- }
- }
- })
-
- // Note: this does not restore values on a back click; setting type and entity type+name is easy,
- // but relevant config lines is a little bit more tedious
- ModalWizard.StepCreate = Backbone.View.extend({
- className:'modal-body',
- events:{
- 'click #add-app-entity':'addEntityBox',
- 'click .editable-entity-heading':'expandEntity',
- 'click .remove-entity-button':'removeEntityClick',
- 'click .editable-entity-button':'saveEntityClick',
- 'click #remove-config':'removeConfigRow',
- 'click #add-config':'addConfigRow',
- 'click .template-lozenge':'templateClick',
- 'keyup .text-filter input':'applyFilter',
- 'change .text-filter input':'applyFilter',
- 'paste .text-filter input':'applyFilter',
- 'keyup #yaml_code':'onYamlCodeChange',
- 'change #yaml_code':'onYamlCodeChange',
- 'paste #yaml_code':'onYamlCodeChange',
- 'shown a[data-toggle="tab"]':'onTabChange',
- 'click #templateTab #catalog-add':'switchToCatalogAdd',
- 'click #templateTab #catalog-yaml':'showYamlTab'
- },
- template:_.template(CreateHtml),
- wizard: null,
- initialize:function () {
- var self = this
- self.catalogEntityIds = []
-
- this.$el.html(this.template({}))
-
- // for building from entities
- this.addEntityBox()
-
- // TODO: Make into models, allow options to override, then pass in in test
- // with overrridden url. Can then think about fixing tests in application-add-wizard-spec.js.
- $.get('/v1/catalog/entities', {}, function (result) {
- self.catalogEntityItems = result
- self.catalogEntityIds = _.map(result, function(item) { return item.id })
- self.$(".entity-type-input").typeahead().data('typeahead').source = self.catalogEntityIds
- })
- this.options.catalog.applications = new CatalogApplication.Collection();
- this.options.catalog.applications.fetch({
- data: $.param({
- allVersions: true
- }),
- success: function (collection, response, options) {
- self.$("#appClassTab .application-type-input").typeahead().data('typeahead').source = collection.getTypes();
- $('#catalog-applications-throbber').hide();
- $('#catalog-applications-empty').hide();
- if (collection.size() > 0) {
- self.addTemplateLozenges()
- } else {
- $('#catalog-applications-empty').show();
- self.showYamlTab();
- }
- }
- });
- },
- renderConfiguredEntities:function () {
- var $configuredEntities = this.$('#entitiesAccordionish').empty()
- var that = this
- if (this.model.spec.get("entities") && this.model.spec.get("entities").length > 0) {
- _.each(this.model.spec.get("entities"), function (entity) {
- that.addEntityHtml($configuredEntities, entity)
- })
- }
- },
- updateForState: function () {},
- render:function () {
- this.renderConfiguredEntities()
- this.delegateEvents()
- return this
- },
- onTabChange: function(e) {
- var tabText = $(e.target).text();
- if (tabText=="Catalog") {
- $("li.text-filter").show()
- } else {
- $("li.text-filter").hide()
- }
-
- if (tabText=="YAML") {
- this.model.mode = "yaml";
- } else if (tabText=="Template") {
- this.model.mode = "template";
- } else {
- this.model.mode = "other";
- }
-
- if (this.options.wizard)
- this.options.wizard.updateButtonVisibility();
- },
- onYamlCodeChange: function() {
- if (this.options.wizard)
- this.options.wizard.updateButtonVisibility();
- },
- switchToCatalogAdd: function() {
- var $modal = $('.add-app #modal-container .modal')
- $modal.modal('hide');
- window.location.href="#v1/catalog/new";
- },
- showYamlTab: function() {
- $("ul#app-add-wizard-create-tab").find("a[href='#yamlTab']").tab('show')
- $("#yaml_code").focus();
- },
- applyFilter: function(e) {
- var filter = $(e.currentTarget).val().toLowerCase()
- if (!filter) {
- $(".template-lozenge").show()
- } else {
- _.each($(".template-lozenge"), function(it) {
- var viz = $(it).text().toLowerCase().indexOf(filter)>=0
- if (viz)
- $(it).show()
- else
- $(it).hide()
- })
- }
- },
- addTemplateLozenges: function(event) {
- var that = this
- _.each(this.options.catalog.applications.getDistinctApplications(), function(item) {
- that.addTemplateLozenge(that, item[0])
- })
- },
- addTemplateLozenge: function(that, item) {
- var $tempel = _.template(CreateStepTemplateEntryHtml, {
- id: item.get('id'),
- type: item.get('type'),
- name: item.get('name') || item.get('id'),
- description: item.get('description'),
- planYaml: item.get('planYaml'),
- iconUrl: item.get('iconUrl')
- })
- $("#create-step-template-entries", that.$el).append($tempel)
- },
- templateClick: function(event) {
- var $tl = $(event.target).closest(".template-lozenge");
- var wasSelected = $tl.hasClass("selected")
- $(".template-lozenge").removeClass("selected")
- if (!wasSelected) {
- $tl.addClass("selected")
- this.selectedTemplate = {
- id: $tl.attr('id'),
- type: $tl.data('type'),
- name: $tl.data("name"),
- yaml: $tl.data("yaml"),
- };
- if (this.selectedTemplate.yaml) {
- $("textarea#yaml_code").val(this.selectedTemplate.yaml);
- } else {
- $("textarea#yaml_code").val("services:\n- type: "+this.selectedTemplate.type);
- }
- } else {
- this.selectedTemplate = null;
- }
-
- if (this.options.wizard)
- this.options.wizard.updateButtonVisibility();
- },
- expandEntity:function (event) {
- $(event.currentTarget).next().show('fast').delay(1000).prev().hide('slow')
- },
- saveEntityClick:function (event) {
- this.saveEntity($(event.currentTarget).closest(".editable-entity-group"));
- },
- saveEntity:function ($entityGroup) {
- var that = this
- var name = $('#entity-name',$entityGroup).val()
- var type = $('#entity-type',$entityGroup).val()
- if (type=="" || !_.contains(that.catalogEntityIds, type)) {
- that.showFailure("Missing or invalid type");
- return false
- }
- var saveTarget = this.model.spec.get("entities")[$entityGroup.index()];
- this.model.spec.set("type", null)
- saveTarget.name = name
- saveTarget.type = type
- saveTarget.config = this.getConfigMap($entityGroup)
-
- if (name=="") name=type;
- if (name=="") name="<i>(new entity)</i>";
- $('#entity-name-header',$entityGroup).html( name )
- $('.editable-entity-body',$entityGroup).prev().show('fast').next().hide('fast')
- return true;
- },
- getConfigMap:function (root) {
- var map = {}
- $('.app-add-wizard-config-entry',root).each( function (index,elt) {
- var value = getConvertedConfigValue($('#value',elt).val());
- if (value !== null) {
- map[$('#key',elt).val()] = value;
- }
- })
- return map;
- },
- saveTemplate:function () {
- if (!this.selectedTemplate) return false
- var type = this.selectedTemplate.type;
- if (!this.options.catalog.applications.hasType(type)) {
- $('.entity-info-message').show('slow').delay(2000).hide('slow')
- return false
- }
-
- this.model.spec.set("type", type);
- this.model.name = this.selectedTemplate.name;
- this.model.catalogEntityData = "LOAD"
- return true;
- },
- saveAppClass:function () {
- var that = this
- var tab = $.find('#appClassTab')
- var type = $(tab).find('#app-java-type').val()
- if (!this.options.catalog.applications.hasType(type)) {
- $('.entity-info-message').show('slow').delay(2000).hide('slow')
- return false
- }
- this.model.spec.set("type", type);
- return true;
- },
- addEntityBox:function () {
- var entity = new Entity.Model
- this.model.spec.addEntity( entity )
- this.addEntityHtml($('#entitiesAccordionish', this.$el), entity)
- },
- addEntityHtml:function (parent, entity) {
- var $entity = _.template(CreateEntityEntryHtml, {})
- var that = this
- parent.append($entity)
- parent.children().last().find('.entity-type-input').typeahead({ source: that.catalogEntityIds })
- },
- removeEntityClick:function (event) {
- var $entityGroup = $(event.currentTarget).parent().parent().parent();
- this.model.spec.removeEntityIndex($entityGroup.index())
- $entityGroup.remove()
- },
-
- addConfigRow:function (event) {
- var $row = _.template(EditConfigEntryHtml, {})
- $(event.currentTarget).parent().prev().append($row)
- },
- removeConfigRow:function (event) {
- $(event.currentTarget).parent().remove()
- },
-
- validate:function () {
- var that = this
- var tabName = $('#app-add-wizard-create-tab li[class="active"] a').attr('href')
- if (tabName=='#entitiesTab') {
- delete this.model.spec.attributes["id"]
- var allokay = true
- $($.find('.editable-entity-group')).each(
- function (i,$entityGroup) {
- allokay = that.saveEntity($($entityGroup)) & allokay
- })
- if (!allokay) return false;
- if (this.model.spec.get("entities") && this.model.spec.get("entities").length > 0) {
- this.model.spec.set("type", null);
- return true;
- }
- } else if (tabName=='#templateTab') {
- delete this.model.spec.attributes["id"]
- if (this.saveTemplate()) {
- this.model.spec.set("entities", []);
- return true
- }
- } else if (tabName=='#appClassTab') {
- delete this.model.spec.attributes["id"]
- if (this.saveAppClass()) {
- this.model.spec.set("entities", []);
- return true
- }
- } else if (tabName=='#yamlTab') {
- this.model.yaml = this.$("#yaml_code").val();
- if (this.model.yaml) {
- return true;
- }
- } else {
- console.info("NOT IMPLEMENTED YET")
- // TODO - other tabs not implemented yet
- // do nothing, show error return false below
- }
- this.showFailure("Invalid application type/spec");
- return false
- },
-
- showFailure: function(text) {
- if (!text) text = "Failure performing the specified action";
- this.$('div.error-message .error-message-text').html(_.escape(text));
- this.$('div.error-message').slideDown(250).delay(10000).slideUp(500);
- }
-
- })
-
- ModalWizard.StepDeploy = Backbone.View.extend({
- className:'modal-body',
-
- events:{
- 'click #add-selector-container':'addLocation',
- 'click #remove-app-location':'removeLocation',
- 'change .select-version': 'selectionVersion',
- 'change .select-location': 'selectionLocation',
- 'blur #application-name':'updateName',
- 'click #remove-config':'removeConfigRow',
- 'click #add-config':'addConfigRow'
- },
-
- template:_.template(DeployHtml),
- versionOptionTemplate:_.template(DeployVersionOptionHtml),
- locationRowTemplate:_.template(DeployLocationRowHtml),
- locationOptionTemplate:_.template(DeployLocationOptionHtml),
-
- initialize:function () {
- this.model.spec.on("change", this.render, this)
- this.$el.html(this.template())
- this.locations = new Location.Collection()
- },
- beforeClose:function () {
- this.model.spec.off("change", this.render)
- },
- renderName:function () {
- this.$('#application-name').val(this.model.spec.get("name"))
- },
- renderVersions: function() {
- var optionTemplate = this.versionOptionTemplate
- select = this.$('.select-version')
- container = this.$('#app-versions')
- defaultVersion = '0.0.0.SNAPSHOT';
-
- select.empty();
-
- var versions = this.options.catalog.applications.getVersions(this.model.spec.get('type'));
- for (var vi = 0; vi < versions.length; vi++) {
- var version = versions[vi];
- select.append(optionTemplate({
- version: version
- }));
- }
-
- if (versions.length === 1 && versions[0] === defaultVersion) {
- this.model.spec.set('version', '');
- container.hide();
- } else {
- this.model.spec.set('version', versions[0]);
- container.show();
- }
- },
- renderAddedLocations:function () {
- // renders the locations added to the model
- var rowTemplate = this.locationRowTemplate,
- optionTemplate = this.locationOptionTemplate,
- container = this.$("#selector-container-location");
- container.empty();
- for (var li = 0; li < this.model.spec.get("locations").length; li++) {
- var chosenLocation = this.model.spec.get("locations")[li];
- container.append(rowTemplate({
- initialValue: chosenLocation,
- rowId: li
- }));
- }
- var $locationOptions = container.find('.select-location');
- var templated = this.locations.map(function(aLocation) {
- return optionTemplate({
- id: aLocation.id || "",
- name: aLocation.getPrettyName()
- });
- });
-
- // insert "none" location
- $locationOptions.append(templated.join(""));
- $locationOptions.each(function(i) {
- var option = $($locationOptions[i]);
- option.val(option.parent().attr('initialValue'));
- // Only append dashes if there are any locations
- if (option.find("option").length > 0) {
- option.append("<option disabled>------</option>");
- }
- option.append(optionTemplate({
- id: NO_LOCATION_INDICATOR,
- name: "None"
- }));
- });
- },
- render:function () {
- this.delegateEvents()
- return this
- },
- updateForState: function () {
- var that = this
- // clear any error message (we are being displayed fresh; if there are errors in the update, we'll show them in code below)
- this.$('div.error-message').hide();
- this.renderName()
- this.renderVersions()
- this.locations.fetch({
- success:function () {
- if (that.model.spec.get("locations").length==0)
- that.addLocation()
- else
- that.renderAddedLocations()
- }})
-
- if (this.model.catalogEntityData==null) {
- this.renderStaticConfig(null)
- } else if (this.model.catalogEntityData=="LOAD") {
- this.renderStaticConfig("LOADING")
- $.get('/v1/catalog/entities/'+this.model.spec.get("type"), {}, function (result) {
- that.model.catalogEntityData = result
- that.renderStaticConfig(that.model.catalogEntityData)
- })
- } else {
- this.renderStaticConfig(this.model.catalogEntityData)
- }
- },
- addLocation:function () {
- if (this.locations.models.length>0) {
- this.model.spec.addLocation(this.locations.models[0].get("id"))
- } else {
- // i.e. No location
- this.model.spec.addLocation(undefined);
- }
- this.renderAddedLocations()
- },
- removeLocation:function (event) {
- var toBeRemoved = $(event.currentTarget).parent().attr('rowId')
- this.model.spec.removeLocationIndex(toBeRemoved)
- this.renderAddedLocations()
- },
- addConfigRow:function (event) {
- var $row = _.template(EditConfigEntryHtml, {})
- $(event.currentTarget).parent().prev().append($row)
- },
- removeConfigRow:function (event) {
- $(event.currentTarget).parent().parent().remove()
- },
- renderStaticConfig:function (catalogEntryItem) {
- this.$('.config-table').html('')
- if (catalogEntryItem=="LOADING") {
- this.$('.required-config-loading').show()
- } else {
- var configs = []
- this.$('.required-config-loading').hide()
- if (catalogEntryItem!=null && catalogEntryItem.config!=null) {
- var that = this
- _.each(catalogEntryItem.config, function (cfg) {
- if (cfg.priority !== undefined) {
- var html = _.template(RequiredConfigEntryHtml, {data:cfg});
- that.$('.config-table').append(html)
- }
- })
- }
- }
- },
- getConfigMap:function() {
- var map = {};
- $('.app-add-wizard-config-entry').each( function (index,elt) {
- var value = $('#checkboxValue',elt).length ? $('#checkboxValue',elt).is(':checked') :
- getConvertedConfigValue($('#value',elt).val());
- if (value !== null) {
- map[$('#key',elt).val()] = value;
- }
- })
- return map;
- },
- selectionVersion:function (event) {
- this.model.spec.set("version", $(event.currentTarget).val())
- },
- selectionLocation:function (event) {
- var loc_id = $(event.currentTarget).val(),
- isNoneLocation = loc_id === NO_LOCATION_INDICATOR;
- var locationValid = isNoneLocation || this.locations.find(function (candidate) {
- return candidate.get("id")==loc_id;
- });
- if (!locationValid) {
- log("invalid location "+loc_id);
- this.showFailure("Invalid location "+loc_id);
- this.model.spec.set("locations",[]);
- } else {
- var index = $(event.currentTarget).parent().attr('rowId');
- this.model.spec.setLocationAtIndex(index, isNoneLocation ? undefined : loc_id);
- }
- },
- updateName:function () {
- var name = this.$('#application-name').val();
- if (name)
- this.model.spec.set("name", name);
- else
- this.model.spec.set("name", "");
- },
- validate:function () {
- this.model.spec.set("config", this.getConfigMap())
- if (this.model.spec.get("locations").length !== 0) {
- return true
- } else {
- this.showFailure("A location is required");
- return false;
- }
- },
- showFailure: function(text) {
- if (!text) text = "Failure performing the specified action";
- log("showing error: "+text);
- this.$('div.error-message .error-message-text').html(_.escape(text));
- // flash the error, but make sure it goes away (we do not currently have any other logic for hiding this error message)
- this.$('div.error-message').slideDown(250).delay(10000).slideUp(500);
- }
- })
-
- return ModalWizard
-})
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js
----------------------------------------------------------------------
diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js b/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js
deleted file mode 100644
index e9c23bc..0000000
--- a/brooklyn-ui/src/main/webapp/assets/js/view/application-explorer.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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 should render the main content in the Application Explorer page.
- * Components on this page should be rendered as sub-views.
- * @type {*}
- */
-define([
- "underscore", "jquery", "backbone", "view/viewutils",
- "./application-add-wizard", "model/application", "model/entity-summary", "model/app-tree", "./application-tree", "./entity-details",
- "text!tpl/apps/details.html", "text!tpl/apps/entity-not-found.html", "text!tpl/apps/page.html"
-], function (_, $, Backbone, ViewUtils,
- AppAddWizard, Application, EntitySummary, AppTree, ApplicationTreeView, EntityDetailsView,
- EntityDetailsEmptyHtml, EntityNotFoundHtml, PageHtml) {
-
- var ApplicationExplorerView = Backbone.View.extend({
- tagName:"div",
- className:"container container-fluid",
- id:'application-explorer',
- template:_.template(PageHtml),
- notFoundTemplate: _.template(EntityNotFoundHtml),
- events:{
- 'click .application-tree-refresh': 'refreshApplicationsInPlace',
- 'click #add-new-application':'createApplication',
- 'click .delete':'deleteApplication'
- },
- initialize: function () {
- this.$el.html(this.template({}))
- $(".nav1").removeClass("active");
- $(".nav1_apps").addClass("active");
-
- this.treeView = new ApplicationTreeView({
- collection:this.collection,
- appRouter:this.options.appRouter
- })
- this.treeView.on('entitySelected', function(e) {
- this.displayEntityId(e.id, e.get('applicationId'), false);
- }, this);
- this.$('div#app-tree').html(this.treeView.renderFull().el)
- this.$('div#details').html(EntityDetailsEmptyHtml);
-
- ViewUtils.fetchRepeatedlyWithDelay(this, this.collection)
- },
- refreshApplicationsInPlace: function() {
- // fetch without reset sets of change events, which now get handled correctly
- // (not a full visual recompute, which reset does - both in application-tree.js)
- this.collection.fetch();
- },
- beforeClose: function () {
- this.collection.off("reset", this.render);
- this.treeView.close();
- if (this.detailsView)
- this.detailsView.close();
- },
- show: function(entityId) {
- var tab = "";
- var tabDetails = "";
- if (entityId) {
- if (entityId[0]=='/') entityId = entityId.substring(1);
- var slash = entityId.indexOf('/');
- if (slash>0) {
- tab = entityId.substring(slash+1)
- entityId = entityId.substring(0, slash);
- }
- }
- if (tab) {
- var slash = tab.indexOf('/');
- if (slash>0) {
- tabDetails = tab.substring(slash+1)
- tab = tab.substring(0, slash);
- }
- this.preselectTab(tab, tabDetails);
- }
- this.treeView.selectEntity(entityId)
- },
- createApplication:function () {
- var that = this;
- if (this._modal) {
- this._modal.close()
- }
- var wizard = new AppAddWizard({
- appRouter:that.options.appRouter,
- callback:function() { that.refreshApplicationsInPlace() }
- })
- this._modal = wizard
- this.$(".add-app #modal-container").html(wizard.render().el)
- this.$(".add-app #modal-container .modal")
- .on("hidden",function () {
- wizard.close()
- }).modal('show')
- },
- deleteApplication:function (event) {
- // call Backbone destroy() which does HTTP DELETE on the model
- this.collection.get(event.currentTarget['id']).destroy({wait:true})
- },
- /**
- * Causes the tab with the given name to be selected automatically when
- * the view is next rendered.
- */
- preselectTab: function(tab, tabDetails) {
- this.currentTab = tab;
- this.currentTabDetails = tabDetails;
- },
- showDetails: function(app, entitySummary) {
- var that = this;
- ViewUtils.cancelFadeOnceLoaded($("div#details"));
-
- var whichTab = this.currentTab;
- if (!whichTab) {
- whichTab = "summary";
- if (this.detailsView) {
- whichTab = this.detailsView.$el.find(".tab-pane.active").attr("id");
- this.detailsView.close();
- }
- }
- if (this.detailsView) {
- this.detailsView.close();
- }
- this.detailsView = new EntityDetailsView({
- model: entitySummary,
- application: app,
- appRouter: this.options.appRouter,
- preselectTab: whichTab,
- preselectTabDetails: this.currentTabDetails,
- });
-
- this.detailsView.on("entity.expunged", function() {
- that.preselectTab("summary");
- var id = that.selectedEntityId;
- var model = that.collection.get(id);
- if (model && model.get("parentId")) {
- that.displayEntityId(model.get("parentId"));
- } else if (that.collection) {
- that.displayEntityId(that.collection.first().id);
- } else if (id) {
- that.displayEntityNotFound(id);
- } else {
- that.displayEntityNotFound("?");
- }
- that.collection.fetch();
- });
- this.detailsView.render( $("div#details") );
- },
- displayEntityId: function (id, appName, afterLoad) {
- var that = this;
- var entityLoadFailed = function() {
- return that.displayEntityNotFound(id);
- };
- if (appName === undefined) {
- if (!afterLoad) {
- // try a reload if given an ID we don't recognise
- this.collection.includeEntities([id]);
- this.collection.fetch({
- success: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); },
- error: function() { _.defer(function() { that.displayEntityId(id, appName, true); }); }
- });
- ViewUtils.fadeToIndicateInitialLoad($("div#details"))
- return;
- } else {
- // no such app
- entityLoadFailed();
- return;
- }
- }
-
- var app = new Application.Model();
- var entitySummary = new EntitySummary.Model;
-
- app.url = "/v1/applications/" + appName;
- entitySummary.url = "/v1/applications/" + appName + "/entities/" + id;
-
- // in case the server response time is low, fade out while it refreshes
- // (since we can't show updated details until we've retrieved app + entity details)
- ViewUtils.fadeToIndicateInitialLoad($("div#details"));
-
- $.when(app.fetch(), entitySummary.fetch())
- .done(function() {
- that.showDetails(app, entitySummary);
- })
- .fail(entityLoadFailed);
- },
- displayEntityNotFound: function(id) {
- $("div#details").html(this.notFoundTemplate({"id": id}));
- ViewUtils.cancelFadeOnceLoaded($("div#details"))
- },
- })
-
- return ApplicationExplorerView
-})
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js
----------------------------------------------------------------------
diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js b/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js
deleted file mode 100644
index 2f7a3d0..0000000
--- a/brooklyn-ui/src/main/webapp/assets/js/view/application-tree.js
+++ /dev/null
@@ -1,367 +0,0 @@
-/*
- * 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.
-*/
-/**
- * Sub-View to render the Application tree.
- * @type {*}
- */
-define([
- "underscore", "jquery", "backbone", "view/viewutils",
- "model/app-tree", "text!tpl/apps/tree-item.html", "text!tpl/apps/tree-empty.html"
-], function (_, $, Backbone, ViewUtils,
- AppTree, TreeItemHtml, EmptyTreeHtml) {
-
- var emptyTreeTemplate = _.template(EmptyTreeHtml);
- var treeItemTemplate = _.template(TreeItemHtml);
-
- var findAllTreeboxes = function(id, $scope) {
- return $('.tree-box[data-entity-id="' + id + '"]', $scope);
- };
-
- var findRootTreebox = function(id) {
- return $('.lozenge-app-tree-wrapper').children('.tree-box[data-entity-id="' + id + '"]', this.$el);
- };
-
- var findChildTreebox = function(id, $parentTreebox) {
- return $parentTreebox.children('.node-children').children('.tree-box[data-entity-id="' + id + '"]');
- };
-
- var findMasterTreebox = function(id, $scope) {
- return $('.tree-box[data-entity-id="' + id + '"]:not(.indirect)', $scope);
- };
-
- var createEntityTreebox = function(id, name, $domParent, depth, indirect) {
- // Tildes in sort key force entities with no name to bottom of list (z < ~).
- var sortKey = (name ? name.toLowerCase() : "~~~") + " " + id.toLowerCase();
-
- // Create the wrapper.
- var $treebox = $(
- '<div data-entity-id="'+id+'" data-sort-key="'+sortKey+'" data-depth="'+depth+'" ' +
- 'class="tree-box toggler-group' +
- (indirect ? " indirect" : "") +
- (depth == 0 ? " outer" : " inner " + (depth % 2 ? " depth-odd" : " depth-even")+
- (depth == 1 ? " depth-first" : "")) + '">'+
- '<div class="entity_tree_node_wrapper"></div>'+
- '<div class="node-children toggler-target hide"></div>'+
- '</div>');
-
- // Insert into the passed DOM parent, maintaining sort order relative to siblings: name then id.
- var placed = false;
- var contender = $(".toggler-group", $domParent).first();
- while (contender.length && !placed) {
- var contenderKey = contender.data("sort-key");
- if (sortKey < contenderKey) {
- contender.before($treebox);
- placed = true;
- } else {
- contender = contender.next(".toggler-group", $domParent);
- }
- }
- if (!placed) {
- $domParent.append($treebox);
- }
- return $treebox;
- };
-
- var getOrCreateApplicationTreebox = function(id, name, treeView) {
- var $treebox = findRootTreebox(id);
- if (!$treebox.length) {
- var $insertionPoint = $('.lozenge-app-tree-wrapper', treeView.$el);
- if (!$insertionPoint.length) {
- // entire view must be created
- treeView.$el.html(
- '<div class="navbar_main_wrapper treeloz">'+
- '<div id="tree-list" class="navbar_main treeloz">'+
- '<div class="lozenge-app-tree-wrapper">'+
- '</div></div></div>');
- $insertionPoint = $('.lozenge-app-tree-wrapper', treeView.$el);
- }
- $treebox = createEntityTreebox(id, name, $insertionPoint, 0, false);
- }
- return $treebox;
- };
-
- var getOrCreateChildTreebox = function(id, name, isIndirect, $parentTreebox) {
- var $treebox = findChildTreebox(id, $parentTreebox);
- if (!$treebox.length) {
- $treebox = createEntityTreebox(id, name, $parentTreebox.children('.node-children'), $parentTreebox.data("depth") + 1, isIndirect);
- }
- return $treebox;
- };
-
- var updateTreeboxContent = function(entity, $treebox, treeView) {
- var $newContent = $(treeView.template({
- id: entity.get('id'),
- parentId: entity.get('parentId'),
- model: entity,
- statusIconUrl: ViewUtils.computeStatusIconInfo(entity.get("serviceUp"), entity.get("serviceState")).url,
- indirect: $treebox.hasClass('indirect'),
- }));
-
- var $wrapper = $treebox.children('.entity_tree_node_wrapper');
-
- // Preserve old display status (just chevron direction at present).
- if ($wrapper.find('.tree-node-state').hasClass('icon-chevron-down')) {
- $newContent.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
- }
-
- $wrapper.html($newContent);
- addEventsToNode($treebox, treeView);
- };
-
- var addEventsToNode = function($node, treeView) {
- // show the "light-popup" (expand / expand all / etc) menu
- // if user hovers for 500ms. surprising there is no option for this (hover delay).
- // also, annoyingly, clicks around the time the animation starts don't seem to get handled
- // if the click is in an overlapping reason; this is why we position relative top: 12px in css
- $('.light-popup', $node).parent().parent().hover(
- function(parent) {
- treeView.cancelHoverTimer();
- treeView.hoverTimer = setTimeout(function() {
- var menu = $(parent.currentTarget).find('.light-popup');
- menu.show();
- }, 500);
- },
- function(parent) {
- treeView.cancelHoverTimer();
- $('.light-popup').hide();
- }
- );
- };
-
- var selectTreebox = function(id, $treebox, treeView) {
- $('.entity_tree_node_wrapper').removeClass('active');
- $treebox.children('.entity_tree_node_wrapper').addClass('active');
-
- var entity = treeView.collection.get(id);
- if (entity) {
- treeView.selectedEntityId = id;
- treeView.trigger('entitySelected', entity);
- }
- };
-
-
- return Backbone.View.extend({
- template: treeItemTemplate,
- hoverTimer: null,
-
- events: {
- 'click span.entity_tree_node .tree-change': 'treeChange',
- 'click span.entity_tree_node': 'nodeClicked'
- },
-
- initialize: function() {
- this.collection.on('add', this.entityAdded, this);
- this.collection.on('change', this.entityChanged, this);
- this.collection.on('remove', this.entityRemoved, this);
- this.collection.on('reset', this.renderFull, this);
- _.bindAll(this);
- },
-
- beforeClose: function() {
- this.collection.off("reset", this.renderFull);
- },
-
- entityAdded: function(entity) {
- // Called when the full entity model is fetched into our collection, at which time we can replace
- // the empty contents of any placeholder tree nodes (.tree-box) that were created earlier.
- // The entity may have multiple 'treebox' views (in the case of group members).
-
- // If the new entity is an application, we must create its placeholder in the DOM.
- if (!entity.get('parentId')) {
- var $treebox = getOrCreateApplicationTreebox(entity.id, entity.get('name'), this);
-
- // Select the new app if there's no current selection.
- if (!this.selectedEntityId)
- selectTreebox(entity.id, $treebox, this);
- }
-
- this.entityChanged(entity);
- },
-
- entityChanged: function(entity) {
- // The entity may have multiple 'treebox' views (in the case of group members).
- var that = this;
- findAllTreeboxes(entity.id).each(function() {
- var $treebox = $(this);
- updateTreeboxContent(entity, $treebox, that);
- });
- },
-
- entityRemoved: function(entity) {
- // The entity may have multiple 'treebox' views (in the case of group members).
- findAllTreeboxes(entity.id, this.$el).remove();
- // Collection seems sometimes to retain children of the removed node;
- // not sure why, but that's okay for now.
- if (this.collection.getApplications().length == 0)
- this.renderFull();
- },
-
- nodeClicked: function(event) {
- var $treebox = $(event.currentTarget).closest('.tree-box');
- var id = $treebox.data('entityId');
- selectTreebox(id, $treebox, this);
- return false;
- },
-
- selectEntity: function(id) {
- var $treebox = findMasterTreebox(id, this.$el);
- selectTreebox(id, $treebox, this);
- },
-
- renderFull: function() {
- var that = this;
- this.$el.empty();
-
- // Display tree and highlight the selected entity.
- if (this.collection.getApplications().length == 0) {
- this.$el.append(emptyTreeTemplate());
-
- } else {
- _.each(this.collection.getApplications(), function(appId) {
- var entity = that.collection.get(appId);
- var $treebox = getOrCreateApplicationTreebox(entity.id, entity.name, that);
- updateTreeboxContent(entity, $treebox, that);
- });
- }
-
- // Select the first app if there's no current selection.
- if (!this.selectedEntityId) {
- var firstApp = _.first(this.collection.getApplications());
- if (firstApp)
- this.selectEntity(firstApp);
- }
- return this;
- },
-
- cancelHoverTimer: function() {
- if (this.hoverTimer != null) {
- clearTimeout(this.hoverTimer);
- this.hoverTimer = null;
- }
- },
-
- treeChange: function(event) {
- var $target = $(event.currentTarget);
- var $treeBox = $target.closest('.tree-box');
- if ($target.hasClass('tr-expand')) {
- this.showChildrenOf($treeBox, false);
- } else if ($target.hasClass('tr-expand-all')) {
- this.showChildrenOf($treeBox, true);
- } else if ($target.hasClass('tr-collapse')) {
- this.hideChildrenOf($treeBox, false);
- } else if ($target.hasClass('tr-collapse-all')) {
- this.hideChildrenOf($treeBox, true);
- } else {
- // default - toggle
- if ($treeBox.children('.node-children').is(':visible')) {
- this.hideChildrenOf($treeBox, false);
- } else {
- this.showChildrenOf($treeBox, false);
- }
- }
- // hide the popup menu
- this.cancelHoverTimer();
- $('.light-popup').hide();
- // don't let other events interfere
- return false;
- },
-
- showChildrenOf: function($treeBox, recurse, excludedEntityIds) {
- excludedEntityIds = excludedEntityIds || [];
- var idToExpand = $treeBox.data('entityId');
- var $wrapper = $treeBox.children('.entity_tree_node_wrapper');
- var $childContainer = $treeBox.children('.node-children');
- var model = this.collection.get(idToExpand);
- if (model == null) {
- // not yet loaded; parallel thread should load
- return;
- }
-
- var that = this;
- var children = model.get('children'); // entity summaries: {id: ..., name: ...}
- var renderChildrenAsIndirect = $treeBox.hasClass("indirect");
- _.each(children, function(child) {
- var $treebox = getOrCreateChildTreebox(child.id, child.name, renderChildrenAsIndirect, $treeBox);
- var model = that.collection.get(child.id);
- if (model) {
- updateTreeboxContent(model, $treebox, that);
- }
- });
- var members = model.get('members'); // entity summaries: {id: ..., name: ...}
- _.each(members, function(member) {
- var $treebox = getOrCreateChildTreebox(member.id, member.name, true, $treeBox);
- var model = that.collection.get(member.id);
- if (model) {
- updateTreeboxContent(model, $treebox, that);
- }
- });
-
- // Avoid infinite recursive expansion using a "taboo list" of indirect entities already expanded in this
- // operation. Example: a group that contains itself or one of its own ancestors. Such cycles can only
- // originate via "indirect" subordinates.
- var expandIfNotExcluded = function($treebox, excludedEntityIds, defer) {
- if ($treebox.hasClass('indirect')) {
- var id = $treebox.data('entityId');
- if (_.contains(excludedEntityIds, id))
- return;
- excludedEntityIds.push(id);
- }
- var doExpand = function() { that.showChildrenOf($treebox, recurse, excludedEntityIds); };
- if (defer) _.defer(doExpand);
- else doExpand();
- };
-
- if (this.collection.includeEntities(_.union(children, members))) {
- // we have to load entities before we can proceed
- this.collection.fetch({
- success: function() {
- if (recurse) {
- $childContainer.children('.tree-box').each(function () {
- expandIfNotExcluded($(this), excludedEntityIds, true);
- });
- }
- }
- });
- }
-
- $childContainer.slideDown(300);
- $wrapper.find('.tree-node-state').removeClass('icon-chevron-right').addClass('icon-chevron-down');
- if (recurse) {
- $childContainer.children('.tree-box').each(function () {
- expandIfNotExcluded($(this), excludedEntityIds, false);
- });
- }
- },
-
- hideChildrenOf: function($treeBox, recurse) {
- var $wrapper = $treeBox.children('.entity_tree_node_wrapper');
- var $childContainer = $treeBox.children('.node-children');
- if (recurse) {
- var that = this;
- $childContainer.children('.tree-box').each(function () {
- that.hideChildrenOf($(this), recurse);
- });
- }
- $childContainer.slideUp(300);
- $wrapper.find('.tree-node-state').removeClass('icon-chevron-down').addClass('icon-chevron-right');
- },
-
- });
-
-});
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/18b073a9/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js
----------------------------------------------------------------------
diff --git a/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js b/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js
deleted file mode 100644
index 7d4ab2a..0000000
--- a/brooklyn-ui/src/main/webapp/assets/js/view/catalog.js
+++ /dev/null
@@ -1,613 +0,0 @@
-/*
- * 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([
- "underscore", "jquery", "backbone", "brooklyn",
- "model/location", "model/entity",
- "text!tpl/catalog/page.html",
- "text!tpl/catalog/details-entity.html",
- "text!tpl/catalog/details-generic.html",
- "text!tpl/catalog/details-location.html",
- "text!tpl/catalog/add-catalog-entry.html",
- "text!tpl/catalog/add-yaml.html",
- "text!tpl/catalog/add-location.html",
- "text!tpl/catalog/nav-entry.html",
-
- "bootstrap", "jquery-form"
-], function(_, $, Backbone, Brooklyn,
- Location, Entity,
- CatalogPageHtml, DetailsEntityHtml, DetailsGenericHtml, LocationDetailsHtml,
- AddCatalogEntryHtml, AddYamlHtml, AddLocationHtml, EntryHtml) {
-
- // Holds the currently active details type, e.g. applications, policies. Bit of a workaround
- // to share the active view with all instances of AccordionItemView, so clicking the 'reload
- // catalog' button (handled by the parent of the AIVs) does not apply the 'active' class to
- // more than one element.
- var activeDetailsView;
-
- var CatalogItemDetailsView = Backbone.View.extend({
-
- events: {
- "click .delete": "deleteItem"
- },
-
- initialize: function() {
- _.bindAll(this);
- this.options.template = _.template(this.options.template || DetailsGenericHtml);
- },
-
- render: function() {
- if (!this.options.model) {
- return this.renderEmpty();
- } else {
- return this.renderDetails();
- }
- },
-
- renderEmpty: function(extraMessage) {
- this.$el.html("<div class='catalog-details'>" +
- "<h3>Select an entry on the left</h3>" +
- (extraMessage ? extraMessage : "") +
- "</div>");
- return this;
- },
-
- renderDetails: function() {
- var that = this,
- model = this.options.model,
- template = this.options.template;
- var show = function() {
- // Keep the previously open section open between items. Duplication between
- // here and setDetailsView, below. This case handles view refreshes from this
- // view directly (e.g. when indicating an error), below handles keeping the
- // right thing open when navigating from view to view.
- var open = this.$(".in").attr("id");
- var newHtml = $(template({model: model, viewName: that.options.name}));
- $(newHtml).find("#"+open).addClass("in");
- that.$el.html(newHtml);
- // rewire events. previous callbacks are removed automatically.
- that.delegateEvents()
- };
-
- this.activeModel = model;
- // Load the view with currently available data and refresh once the load is complete.
- // Only refreshes the view if the model changes and the user hasn't selected another
- // item while the load was executing.
- show();
- model.on("change", function() {
- if (that.activeModel.cid === model.cid) {
- show();
- }
- });
- model.fetch()
- .fail(function(xhr, textStatus, errorThrown) {
- console.log("error loading", model.id, ":", errorThrown);
- if (that.activeModel.cid === model.cid) {
- model.error = true;
- show();
- }
- })
- // Runs after the change event fires, or after the xhr completes
- .always(function () {
- model.off("change");
- });
- return this;
- },
-
- deleteItem: function(event) {
- // Could use wait flag to block removal of model from collection
- // until server confirms deletion and success handler to perform
- // removal. Useful if delete fails for e.g. lack of entitlement.
- var that = this;
- var displayName = $(event.currentTarget).data("name") || "item";
- this.activeModel.destroy({
- success: function() {
- that.renderEmpty("Deleted " + displayName);
- },
- error: function(info) {
- that.renderEmpty("Unable to permanently delete " + displayName+". Deletion is temporary, client-side only.");
- }
- });
- }
- });
-
- var AddCatalogEntryView = Backbone.View.extend({
- template: _.template(AddCatalogEntryHtml),
- events: {
- "click .show-context": "showContext"
- },
- initialize: function() {
- _.bindAll(this);
- },
- render: function (initialView) {
- this.$el.html(this.template());
- if (initialView) {
- if (initialView == "entity") initialView = "yaml";
-
- this.$("[data-context='"+initialView+"']").addClass("active");
- this.showFormForType(initialView)
- }
- return this;
- },
- clearWithHtml: function(template) {
- if (this.contextView) this.contextView.close();
- this.context = undefined;
- this.$(".btn").removeClass("active");
- this.$("#catalog-add-form").html(template);
- },
- beforeClose: function () {
- if (this.contextView) this.contextView.close();
- },
- showContext: function(event) {
- var $event = $(event.currentTarget);
- var context = $event.data("context");
- if (this.context !== context) {
- if (this.contextView) {
- this.contextView.close();
- }
- this.showFormForType(context)
- }
- },
- showFormForType: function (type) {
- this.context = type;
- if (type == "yaml" || type == "entity") {
- this.contextView = newYamlForm(this, this.options.parent);
- } else if (type == "location") {
- this.contextView = newLocationForm(this, this.options.parent);
- } else if (type !== undefined) {
- console.log("unknown catalog type " + type);
- this.showFormForType("yaml");
- return;
- }
- Backbone.history.navigate("/v1/catalog/new/" + type);
- this.$("#catalog-add-form").html(this.contextView.$el);
- }
- });
-
- function newYamlForm(addView, addViewParent) {
- return new Brooklyn.view.Form({
- template: _.template(AddYamlHtml),
- onSubmit: function (model) {
- var submitButton = this.$(".catalog-submit-button");
- // "loading" is an indicator to Bootstrap, not a string to display
- submitButton.button("loading");
- var self = this;
- var options = {
- url: "/v1/catalog/",
- data: model.get("yaml"),
- processData: false,
- type: "post"
- };
- $.ajax(options)
- .done(function (data, status, xhr) {
- // Can extract location of new item with:
- //model.url = Brooklyn.util.pathOf(xhr.getResponseHeader("Location"));
- if (_.size(data)==0) {
- addView.clearWithHtml( "No items supplied." );
- } else {
- addView.clearWithHtml( "Added: "+_.escape(_.keys(data).join(", "))
- + (_.size(data)==1 ? ". Loading..." : "") );
- addViewParent.loadAnyAccordionItem(_.size(data)==1 ? _.keys(data)[0] : undefined);
- }
- })
- .fail(function (xhr, status, error) {
- submitButton.button("reset");
- self.$(".catalog-save-error")
- .removeClass("hide")
- .find(".catalog-error-message")
- .html(_.escape(Brooklyn.util.extractError(xhr, "Could not add catalog item:\n'n" + error)));
- });
- }
- });
- }
-
- // Could adapt to edit existing locations too.
- function newLocationForm(addView, addViewParent) {
- // Renders with config key list
- var body = new (Backbone.View.extend({
- beforeClose: function() {
- if (this.configKeyList) {
- this.configKeyList.close();
- }
- },
- render: function() {
- this.configKeyList = new Brooklyn.view.ConfigKeyInputPairList().render();
- var template = _.template(AddLocationHtml);
- this.$el.html(template);
- this.$("#new-location-config").html(this.configKeyList.$el);
- },
- showError: function (message) {
- self.$(".catalog-save-error")
- .removeClass("hide")
- .find(".catalog-error-message")
- .html(message);
- }
- }));
- var form = new Brooklyn.view.Form({
- body: body,
- model: Location.Model,
- onSubmit: function (location) {
- var configKeys = body.configKeyList.getConfigKeys();
- if (!configKeys.displayName) {
- configKeys.displayName = location.get("name");
- }
- var submitButton = this.$(".catalog-submit-button");
- // "loading" is an indicator to Bootstrap, not a string to display
- submitButton.button("loading");
- location.set("config", configKeys);
- location.save()
- .done(function (data) {
- addView.clearWithHtml( "Added: "+data.id+". Loading..." );
- addViewParent.loadAccordionItem("locations", data.id);
- })
- .fail(function (response) {
- submitButton.button("reset");
- body.showError(Brooklyn.util.extractError(response));
- });
- }
- });
-
- return form;
- }
-
- var Catalog = Backbone.Collection.extend({
- modelX: Backbone.Model.extend({
- url: function() {
- return "/v1/catalog/" + this.name + "/" + this.id + "?allVersions=true";
- }
- }),
- initialize: function(models, options) {
- this.name = options["name"];
- if (!this.name) {
- throw new Error("Catalog collection must know its name");
- }
- //this.model is a constructor so it shouldn't be _.bind'ed to this
- //It actually works when a browser provided .bind is used, but the
- //fallback implementation doesn't support it.
- var that = this;
- var model = this.model.extend({
- url: function() {
- return "/v1/catalog/" + that.name + "/" + this.id.split(":").join("/");
- }
- });
- _.bindAll(this);
- this.model = model;
- },
- url: function() {
- return "/v1/catalog/" + this.name+"?allVersions=true";
- }
- });
-
- /** Use to fill single accordion view list. */
- var AccordionItemView = Backbone.View.extend({
- tag: "div",
- className: "accordion-item",
- events: {
- 'click .accordion-head': 'toggle',
- 'click .accordion-nav-row': 'showDetails'
- },
- bodyTemplate: _.template(
- "<div class='accordion-head capitalized'><%= name %></div>" +
- "<div class='accordion-body' style='display: <%= display %>'></div>"),
-
- initialize: function() {
- _.bindAll(this);
- this.name = this.options.name;
- if (!this.name) {
- throw new Error("Name should have been given for accordion entry");
- } else if (!this.options.onItemSelected) {
- throw new Error("onItemSelected(model, element) callback should have been given for accordion entry");
- }
-
- // Generic templates
- this.template = _.template(this.options.template || EntryHtml);
-
- // Returns template applied to function arguments. Alter if collection altered.
- // Will be run in the context of the AccordionItemView.
- this.entryTemplateArgs = this.options.entryTemplateArgs || function(model, index) {
- return {type: model.getVersionedAttr("type"), id: model.get("id")};
- };
-
- // undefined argument is used for existing model items
- var collectionModel = this.options.model || Backbone.Model;
- this.collection = this.options.collection || new Catalog(undefined, {
- name: this.name,
- model: collectionModel
- });
- // Refreshes entries list when the collection is synced with the server or
- // any of its members are destroyed.
- this.collection
- .on("sync", this.renderEntries)
- .on("destroy", this.renderEntries);
- this.refresh();
- },
-
- beforeClose: function() {
- this.collection.off();
- },
-
- render: function() {
- this.$el.html(this.bodyTemplate({
- name: this.name,
- display: this.options.autoOpen ? "block" : "none"
- }));
- this.renderEntries();
- return this;
- },
-
- singleItemTemplater: function(isChild, model, index) {
- var args = _.extend({
- cid: model.cid,
- isChild: isChild,
- extraClasses: (activeDetailsView == this.name && model.cid == this.activeCid) ? "active" : ""
- }, this.entryTemplateArgs(model));
- return this.template(args);
- },
-
- renderEntries: function() {
- var elements = this.collection.map(_.partial(this.singleItemTemplater, false), this);
- this.updateContent(elements.join(''));
- },
-
- updateContent: function(markup) {
- this.$(".accordion-body")
- .empty()
- .append(markup);
- },
-
- refresh: function() {
- this.collection.fetch();
- },
-
- showDetails: function(event) {
- var $event = $(event.currentTarget);
- var cid = $event.data("cid");
- if (activeDetailsView !== this.name || this.activeCid !== cid) {
- activeDetailsView = this.name;
- this.activeCid = cid;
- var model = this.collection.get(cid);
- Backbone.history.navigate("v1/catalog/" + this.name + "/" + model.id);
- this.options.onItemSelected(activeDetailsView, model, $event);
- }
- },
-
- toggle: function() {
- var body = this.$(".accordion-body");
- var hidden = this.hidden = body.css("display") == "none";
- if (hidden) {
- body.removeClass("hide").slideDown('fast');
- } else {
- body.slideUp('fast')
- }
- },
-
- show: function() {
- var body = this.$(".accordion-body");
- var hidden = this.hidden = body.css("display") == "none";
- if (hidden) {
- body.removeClass("hide").slideDown('fast');
- }
- }
- });
-
- var AccordionEntityView = AccordionItemView.extend({
- renderEntries: function() {
- var symbolicNameFn = function(model) {return model.get("symbolicName")};
- var groups = this.collection.groupBy(symbolicNameFn);
- var orderedIds = _.uniq(this.collection.map(symbolicNameFn));
-
- function getLatestStableVersion(items) {
- //the server sorts items by descending version, snapshots at the back
- return items[0];
- }
-
- var catalogTree = _.map(orderedIds, function(symbolicName) {
- var group = groups[symbolicName];
- var root = getLatestStableVersion(group);
- var children = _.reject(group, function(model) {return root.id == model.id;});
- return {root: root, children: children};
- });
-
- var templater = function(memo, item, index) {
- memo.push(this.singleItemTemplater(false, item.root));
- return memo.concat(_.map(item.children, _.partial(this.singleItemTemplater, true), this));
- };
-
- var elements = _.reduce(catalogTree, templater, [], this);
- this.updateContent(elements.join(''));
- }
- });
-
- // Controls whole page. Parent of accordion items and details view.
- var CatalogResourceView = Backbone.View.extend({
- tagName:"div",
- className:"container container-fluid",
- entryTemplate:_.template(EntryHtml),
-
- events: {
- 'click .refresh':'refresh',
- 'click #add-new-thing': 'createNewThing'
- },
-
- initialize: function() {
- $(".nav1").removeClass("active");
- $(".nav1_catalog").addClass("active");
- // Important that bind happens before accordion object is created. If it happens after
- // `this' will not be set correctly for the onItemSelected callbacks.
- _.bindAll(this);
- this.accordion = this.options.accordion || {
- "applications": new AccordionEntityView({
- name: "applications",
- singular: "application",
- onItemSelected: _.partial(this.showCatalogItem, DetailsEntityHtml),
- model: Entity.Model,
- autoOpen: !this.options.kind || this.options.kind == "applications"
- }),
- "entities": new AccordionEntityView({
- name: "entities",
- singular: "entity",
- onItemSelected: _.partial(this.showCatalogItem, DetailsEntityHtml),
- model: Entity.Model,
- autoOpen: this.options.kind == "entities"
- }),
- "policies": new AccordionEntityView({
- // TODO needs parsing, and probably its own model
- // but cribbing "entity" works for now
- // (and not setting a model can cause errors intermittently)
- onItemSelected: _.partial(this.showCatalogItem, DetailsEntityHtml),
- name: "policies",
- singular: "policy",
- model: Entity.Model,
- autoOpen: this.options.kind == "policies"
- }),
- "locations": new AccordionItemView({
- name: "locations",
- singular: "location",
- onItemSelected: _.partial(this.showCatalogItem, LocationDetailsHtml),
- collection: this.options.locations,
- autoOpen: this.options.kind == "locations",
- entryTemplateArgs: function (location, index) {
- return {
- type: location.getIdentifierName(),
- id: location.getLinkByName("self")
- };
- }
- })
- };
- },
-
- beforeClose: function() {
- _.invoke(this.accordion, 'close');
- },
-
- render: function() {
- this.$el.html(_.template(CatalogPageHtml, {}));
- var parent = this.$(".catalog-accordion-parent");
- _.each(this.accordion, function(child) {
- parent.append(child.render().$el);
- });
- if (this.options.kind === "new") {
- this.createNewThing(this.options.id);
- } else if (this.options.kind && this.options.id) {
- this.loadAccordionItem(this.options.kind, this.options.id)
- } else {
- // Show empty details view to start
- this.setDetailsView(new CatalogItemDetailsView().render());
- }
- return this
- },
-
- /** Refreshes the contents of each accordion pane */
- refresh: function() {
- _.invoke(this.accordion, 'refresh');
- },
-
- createNewThing: function (type) {
- // Discard if it's the jquery event object.
- if (!_.isString(type)) {
- type = undefined;
- }
- var viewName = "createNewThing";
- if (!type) {
- Backbone.history.navigate("/v1/catalog/new");
- }
- activeDetailsView = viewName;
- this.$(".accordion-nav-row").removeClass("active");
- var newView = new AddCatalogEntryView({
- parent: this
- }).render(type);
- this.setDetailsView(newView);
- },
-
- loadAnyAccordionItem: function (id) {
- this.loadAccordionItem("entities", id);
- this.loadAccordionItem("applications", id);
- this.loadAccordionItem("policies", id);
- this.loadAccordionItem("locations", id);
- },
-
- loadAccordionItem: function (kind, id) {
- if (!this.accordion[kind]) {
- console.error("No accordion for: " + kind);
- } else {
- var accordion = this.accordion[kind];
- var self = this;
- // reset is needed because we rely on server's ordering;
- // without it, server additions are placed at end of list
- accordion.collection.fetch({reset: true})
- .then(function() {
- var model = accordion.collection.get(id);
- if (!model) {
- // if a version is supplied, try it without a version - needed for locations, navigating after deletion
- if (id && id.split(":").length>1) {
- model = accordion.collection.get( id.split(":")[0] );
- }
- }
- if (!model) {
- // if an ID is supplied without a version, look for first matching version (should be newest)
- if (id && id.split(":").length==1 && accordion.collection.models) {
- model = _.find(accordion.collection.models, function(m) {
- return m && m.id && m.id.startsWith(id+":");
- });
- }
- }
- // TODO could look in collection for any starting with ID
- if (model) {
- Backbone.history.navigate("/v1/catalog/"+kind+"/"+id);
- activeDetailsView = kind;
- accordion.activeCid = model.cid;
- accordion.options.onItemSelected(kind, model);
- accordion.show();
- } else {
- // catalog item not found, or not found yet (it might be reloaded and another callback will try again)
- }
- });
- }
- },
-
- showCatalogItem: function(template, viewName, model, $target) {
- this.$(".accordion-nav-row").removeClass("active");
- if ($target) {
- $target.addClass("active");
- } else {
- this.$("[data-cid=" + model.cid + "]").addClass("active");
- }
- var newView = new CatalogItemDetailsView({
- model: model,
- template: template,
- name: viewName
- }).render();
- this.setDetailsView(newView)
- },
-
- setDetailsView: function(view) {
- this.$("#details").html(view.el);
- if (this.detailsView) {
- // Try to re-open sections that were previously visible.
- var openedItem = this.detailsView.$(".in").attr("id");
- if (openedItem) {
- view.$("#" + openedItem).addClass("in");
- }
- this.detailsView.close();
- }
- this.detailsView = view;
- }
- });
-
- return CatalogResourceView
-});