Updated Branches: refs/heads/trunk 06fd82a65 -> 82d26cbe0
AMBARI-3319. Simplify Local Repo setup via UI. (xiwang via yusaku) Project: http://git-wip-us.apache.org/repos/asf/incubator-ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ambari/commit/82d26cbe Tree: http://git-wip-us.apache.org/repos/asf/incubator-ambari/tree/82d26cbe Diff: http://git-wip-us.apache.org/repos/asf/incubator-ambari/diff/82d26cbe Branch: refs/heads/trunk Commit: 82d26cbe05cf02bec5235c794130798d904df26d Parents: 06fd82a Author: Yusaku Sako <[email protected]> Authored: Thu Oct 10 19:41:27 2013 -0700 Committer: Yusaku Sako <[email protected]> Committed: Thu Oct 10 19:41:27 2013 -0700 ---------------------------------------------------------------------- ambari-web/app/config.js | 2 +- ambari-web/app/controllers/installer.js | 70 +++++++++++++ ambari-web/app/messages.js | 4 +- ambari-web/app/routes/installer.js | 21 +++- ambari-web/app/styles/application.less | 63 ++++++++--- ambari-web/app/templates/wizard/step1.hbs | 79 ++++++++------ ambari-web/app/templates/wizard/step2.hbs | 4 +- ambari-web/app/utils/ajax.js | 12 +++ ambari-web/app/views/wizard/step1_view.js | 140 +++++++++++-------------- 9 files changed, 264 insertions(+), 131 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/config.js b/ambari-web/app/config.js index 60ac91c..0a4ee05 100644 --- a/ambari-web/app/config.js +++ b/ambari-web/app/config.js @@ -57,7 +57,7 @@ App.supports = { customizeSmokeTestUser: true, hue: false, ldapGroupMapping: false, - localRepositories: false, + localRepositories: true, highAvailability: true, deleteHost: false, autoRollbackHA: true http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/controllers/installer.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/installer.js b/ambari-web/app/controllers/installer.js index 2fe4f5e..02c0880 100644 --- a/ambari-web/app/controllers/installer.js +++ b/ambari-web/app/controllers/installer.js @@ -357,6 +357,76 @@ App.InstallerController = App.WizardController.extend({ }, /** + * Check validation of the customized local urls + * @param stepController step1WizardController + */ + checkRepoURL: function (stepController) { + var selectedStack = this.get('content.stacks').findProperty('isSelected', true); + selectedStack.set('reload', true); + var nameVersionCombo = selectedStack.name; + var stackName = nameVersionCombo.split('-')[0]; + var stackVersion = nameVersionCombo.split('-')[1]; + if (selectedStack && selectedStack.operatingSystems) { + this.set('validationCnt', selectedStack.operatingSystems.length); + this.set('invalidCnt', 0); + selectedStack.operatingSystems.forEach(function (os) { + os.validation = 'icon-repeat'; + selectedStack.set('reload', !selectedStack.get('reload')); + App.ajax.send({ + name: 'wizard.advanced_repositories.valid_url', + sender: this, + data: { + stackName: stackName, + stackVersion: stackVersion, + nameVersionCombo: nameVersionCombo, + osType: os.osType, + data: { + 'Repositories': { + 'base_url': os.baseUrl + } + } + }, + success: 'checkRepoURLSuccessCallback', + error: 'checkRepoURLErrorCallback' + }); + }, this); + } + }, + setInvalidUrlCnt: function () { + var selectedStack = this.get('content.stacks').findProperty('isSelected', true); + selectedStack.set('invalidCnt', this.get('invalidCnt')); + }.observes('invalidCnt'), + /** + * onSuccess callback for check Repo URL. + */ + checkRepoURLSuccessCallback: function (response, request, data) { + console.log('Success in check Repo URL. data osType: ' + data.osType ); + var selectedStack = this.get('content.stacks').findProperty('isSelected', true); + if (selectedStack && selectedStack.operatingSystems) { + var os = selectedStack.operatingSystems.findProperty('osType', data.osType); + os.validation = 'icon-ok'; + selectedStack.set('reload', !selectedStack.get('reload')); + this.set('validationCnt', this.get('validationCnt') - 1); + } + }, + + /** + * onError callback for check Repo URL. + */ + checkRepoURLErrorCallback: function (request, ajaxOptions, error, data) { + console.log('Error in check Repo URL. The baseURL sent is: ' + data.data); + var osType = data.url.split('/')[8]; + var selectedStack = this.get('content.stacks').findProperty('isSelected', true); + if (selectedStack && selectedStack.operatingSystems) { + var os = selectedStack.operatingSystems.findProperty('osType', osType); + os.validation = 'icon-remove'; + selectedStack.set('reload', !selectedStack.get('reload')); + this.set('validationCnt', this.get('validationCnt') - 1); + this.set('invalidCnt', this.get('invalidCnt') + 1); + } + }, + + /** * Load data for all steps until <code>current step</code> */ loadAllPriorSteps: function () { http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index e7d0d03..944c64e 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -263,7 +263,7 @@ Em.I18n.translations = { 'installer.step1.header':'Select Stack', 'installer.step1.body':'Please select the service stack that you want to use to install your Hadoop cluster.', - 'installer.step1.advancedRepo.title':'Advaned Repository Options', + 'installer.step1.advancedRepo.title':'Advanced Repository Options', 'installer.step1.advancedRepo.message':'Specify the repository where software packages will be downloaded from. If your hosts do not have access to the internet, you will have to create a local mirror of the repository accessible by all hosts and specify the Base URL here.', 'installer.step1.advancedRepo.localRepo.error.modifyUrl':'Local repository URL must be modified', 'installer.step1.advancedRepo.localRepo.error.noUrl':'Base URL required for a local repository', @@ -271,6 +271,8 @@ Em.I18n.translations = { 'installer.step1.advancedRepo.localRepo.label.os':'Operating System', 'installer.step1.advancedRepo.localRepo.label.baseUrl':'Repository Base URL', 'installer.step1.advancedRepo.localRepo.label.stack':'Stack', + 'installer.step1.attentionNeeded':'<b>Attention:</b> All repository URLs are required before you can proceed.', + 'installer.step1.invalidURLAttention': '<b>Attention:</b> Please make sure all repository URLs are valid.', 'installer.step2.header':'Install Options', 'installer.step2.body':'Enter the list of hosts to be included in the cluster and provide your SSH key.', http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/routes/installer.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/routes/installer.js b/ambari-web/app/routes/installer.js index 55434e4..c90c82e 100644 --- a/ambari-web/app/routes/installer.js +++ b/ambari-web/app/routes/installer.js @@ -136,10 +136,23 @@ module.exports = Em.Route.extend({ next: function (router) { var wizardStep1Controller = router.get('wizardStep1Controller'); var installerController = router.get('installerController'); - installerController.saveStacks(wizardStep1Controller); - App.db.setService(undefined); - installerController.clearInstallOptions(); - router.transitionTo('step2'); + installerController.checkRepoURL(wizardStep1Controller); + // make sure got all validations feedback and no invalid url, then proceed + var myVar = setInterval( + function(){ + var cnt = installerController.get('validationCnt'); + var invalidCnt = installerController.get('invalidCnt') + if (cnt == 0 && invalidCnt == 0) { // all feedback exist and no invalid url + installerController.saveStacks(wizardStep1Controller); + App.db.setService(undefined); + installerController.clearInstallOptions(); + router.transitionTo('step2'); + clearInterval(myVar); + } else if ( cnt == 0 && invalidCnt != 0) { + clearInterval(myVar); + } + }, + 1000); } }), http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/styles/application.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less index d248819..cd95024 100644 --- a/ambari-web/app/styles/application.less +++ b/ambari-web/app/styles/application.less @@ -4654,23 +4654,56 @@ i.icon-asterisks { } #advancedRepoAccordion{ - #collapseOne{ - .pull-right{ - margin-bottom: 5px; - } - th.os{ - width: 15%; - } - th.actions{ - width: 15%; + .accordion-heading { + background-color: #f0f0f0; + } + .accordion-body { + .table thead { } - .action{ - cursor: pointer; - .icon-minus-sign{ - color: #FF4B4B; - margin-right: 2px; + .table tbody{ + .label-error{ + color: #b94a48; + } + .textfield-error input{ + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + } + .os { + width: 13%; + } + .url { + width: 70%; + } + .url-results { + width: 70%; + .ember-text-field { + width: 100%; + } + } + .validation { + width: 7%; + } + .validation-results { + width: 7%; + padding-top: 11px; + } + .action { + width: 10%; + } + .action-results { + width: 10%; + padding-top: 11px; + a { + cursor: pointer; + } + .icon-undo { + color: rgb(243, 178, 11); + margin-right: 2px; + } } } - } + } http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/templates/wizard/step1.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/wizard/step1.hbs b/ambari-web/app/templates/wizard/step1.hbs index 9995e51..64b9576 100644 --- a/ambari-web/app/templates/wizard/step1.hbs +++ b/ambari-web/app/templates/wizard/step1.hbs @@ -29,44 +29,63 @@ {{#if App.supports.localRepositories}} <div class="accordion" id="advancedRepoAccordion"> <div class="accordion-group"> - <div class="accordion-heading"> - <a class="accordion-toggle" data-toggle="collapse" data-parent="#advancedRepoAccordion" href="#collapseOne"> - {{t installer.step1.advancedRepo.title}} - </a> + <div class="accordion-heading" {{action "onToggleBlock" target="view"}}> + <i {{bindAttr class=":pull-left :accordion-toggle view.isRLCollapsed:icon-caret-right:icon-caret-down"}}></i> + <a class="accordion-toggle">{{t installer.step1.advancedRepo.title}}</a> </div> - <div id="collapseOne" class="accordion-body collapse"> - <div class="accordion-inner"> - <div class="alert alert-info"> - {{t installer.step1.advancedRepo.message}} - </div> - <table class="table table-striped"> - <thead> + <div class="accordion-body collapse in"> + <div class="accordion-inner"> + <div class="alert alert-info"> + {{t installer.step1.advancedRepo.message}} + </div> + <table class="table table-striped"> + <thead> <tr> <th class="os">{{t common.os}}</th> - <th class="baseUrl">{{t installer.step1.advancedRepo.localRepo.column.baseUrl}}</th> + <th class="url">{{t installer.step1.advancedRepo.localRepo.column.baseUrl}}</th> + <th class="validations"></th> <th class="actions"></th> </tr> - </thead> - <tbody> - {{#each localRepo in view.localRepositories}} - <tr> - <td>{{localRepo.osType}}</td> - <td>{{localRepo.baseUrl}}</td> - <td> - <a class="action" {{action "removeLocalRepository" localRepo target="view" }} > - <i class="icon-minus-sign"></i>{{t common.remove}} - </a> - </td> - </tr> - {{/each}} - </tbody> - </table> - <a {{bindAttr class=":btn :pull-right view.isAddOSDisabled:disabled"}} {{action addLocalRepository target="view"}}><i class="icon-plus"></i> Add OS</a> + </thead> + <tbody> + {{#each repo in view.allRepositories}} + <tr> + <td {{bindAttr class=":os repo.empty-error:label-error repo.invalid-error:label-error "}}>{{repo.osType}}</td> + <td {{bindAttr class=":url-results repo.empty-error:textfield-error repo.invalid-error:textfield-error"}}> + {{view Ember.TextField valueBinding="repo.baseUrl"}} + </td> + <td class="validation-results"> + {{#if repo.validation}} + <i {{bindAttr class="repo.validation"}}></i> + {{/if}} + </td> + <td class="action-results"> + {{#if repo.undo}} + <a {{action "undoLocalRepository" repo target="view" }}> + <i class="icon-undo"></i>{{t common.undo}} + </a> + {{/if}} + </td> + </tr> + {{/each}} + </tbody> + </table> + {{#if view.isSubmitDisabled}} + <div class="alert">{{t installer.step1.attentionNeeded}}</div> + {{/if}} + {{#if view.invalidUrlExist}} + <div class="alert">{{t installer.step1.invalidURLAttention}}</div> + {{/if}} + + </div> </div> - </div> </div> </div> {{/if}} <a class="btn pull-left" {{action back}}>← {{t common.back}}</a> -<a class="btn btn-success pull-right" {{action next}}>{{t common.next}} →</a> \ No newline at end of file +{{#if view.isSubmitDisabled}} + <a class="btn btn-success pull-right" disabled="disabled">{{t common.next}} →</a> +{{else}} + <a class="btn btn-success pull-right" {{action next}}>{{t common.next}} →</a> +{{/if}} http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/templates/wizard/step2.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/wizard/step2.hbs b/ambari-web/app/templates/wizard/step2.hbs index 685dc74..3270c5d 100644 --- a/ambari-web/app/templates/wizard/step2.hbs +++ b/ambari-web/app/templates/wizard/step2.hbs @@ -98,9 +98,8 @@ </div> <div class="advancedOptions"> - <h5>{{t installer.step2.advancedOptions.header}}</h5> - {{#unless App.supports.localRepositories}} + <h5>{{t installer.step2.advancedOptions.header}}</h5> <label {{bindAttr class=":checkbox"}}> {{view Ember.Checkbox checkedBinding="content.installOptions.localRepo"}} @@ -113,7 +112,6 @@ {{t installer.step2.localRepo.label_instead}} </label> {{/unless}} - </div> <div class="btn-area"> {{#unless view.parentView.controller.hideBackButton}} http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/utils/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js index d8b8952..d55f372 100644 --- a/ambari-web/app/utils/ajax.js +++ b/ambari-web/app/utils/ajax.js @@ -835,6 +835,18 @@ var urls = { 'real': '/clusters/{cluster}/requests/{requestId}?fields=tasks/*', 'mock': '/data/wizard/{mock}' }, + 'wizard.advanced_repositories.valid_url': { + 'real': '/stacks2/{stackName}/versions/{stackVersion}/operatingSystems/{osType}/repositories/{nameVersionCombo}', + 'mock': '', + 'type': 'PUT', + 'format': function (data) { + return { + type: 'PUT', + async: true, + data: JSON.stringify(data.data) + } + } + }, 'wizard.install_services.add_host_controller.is_retry': { 'real': '/clusters/{cluster}/host_components', 'mock': '', http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/82d26cbe/ambari-web/app/views/wizard/step1_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/wizard/step1_view.js b/ambari-web/app/views/wizard/step1_view.js index f3d9af9..b8d051e 100644 --- a/ambari-web/app/views/wizard/step1_view.js +++ b/ambari-web/app/views/wizard/step1_view.js @@ -31,27 +31,6 @@ App.WizardStep1View = Em.View.extend({ return stacks; }.property('[email protected]'), - isAddOSDisabled: true, - localRepositories: [], - defaultRepositories: [], - refreshRepositoryInfo: function () { - var selectedStack = this.get('controller.content.stacks').findProperty('isSelected', true); - var localRepos = []; - var defaultRepos = []; - if (selectedStack && selectedStack.operatingSystems) { - selectedStack.operatingSystems.forEach(function (os) { - if (os.baseUrl !== os.defaultBaseUrl) { - localRepos.push($.extend({}, os)); - } else { - defaultRepos.push($.extend({}, os)); - } - }); - } - this.set('localRepositories', localRepos); - this.set('defaultRepositories', defaultRepos); - this.set('isAddOSDisabled', defaultRepos.get('length') < 1); - }.observes('[email protected]', '[email protected][email protected]'), - stackRadioButton: Ember.Checkbox.extend({ tagName: 'input', attributeBindings: [ 'type', 'checked' ], @@ -66,68 +45,75 @@ App.WizardStep1View = Em.View.extend({ } }), - removeLocalRepository: function (event) { - var localRepo = event.context; + allRepositories: [], + repoErrorCnt: function () { + return this.get('allRepositories').filterProperty('empty-error', true).length; + }.property('[email protected]'), + loadRepositories: function () { + var selectedStack = this.get('controller.content.stacks').findProperty('isSelected', true); + var repos = []; + if (selectedStack && selectedStack.operatingSystems) { + selectedStack.operatingSystems.forEach(function (os) { + var cur_repo = Em.Object.create({ + baseUrl: os.baseUrl, + defaultBaseUrl: os.defaultBaseUrl, + osType: os.osType, + validation: os.validation + }); + cur_repo.set('empty-error', !os.baseUrl); + cur_repo.set('invalid-error', os.validation == 'icon-remove'); + cur_repo.set('undo', os.baseUrl != os.defaultBaseUrl); + repos.pushObject(cur_repo); + }); + } + this.set('allRepositories', repos); + }.observes('[email protected]', '[email protected]'), + undoLocalRepository: function (event) { + var localRepo = event.context; var selectedStack = this.get('controller.content.stacks').findProperty('isSelected', true); var cos = selectedStack.operatingSystems.findProperty('osType', localRepo.osType); cos.baseUrl = cos.defaultBaseUrl; - - this.refreshRepositoryInfo(); + cos.validation = null; + this.loadRepositories(); }, - - addLocalRepository: function () { - var self = this; - var defaultRepos = self.get('defaultRepositories'); + editLocalRepository: function (event) { + //upload to content + var repos = this.get('allRepositories'); var selectedStack = this.get('controller.content.stacks').findProperty('isSelected', true); - - App.ModalPopup.show({ - // classNames: ['big-modal'], - classNames: [ 'sixty-percent-width-modal' ], - header: "Add Local Repository", - primary: 'Add', - secondary: 'Cancel', - onPrimary: function () { - var error = null; - var childViews = this.get('childViews'); - if (childViews && childViews.get('length') > 0) { - var childView = childViews.objectAt(0); - if (childView) { - if (childView.get('enteredUrl')) { - if (childView.get('selectedOS').baseUrl !== childView.get('enteredUrl') && childView.get('selectedOS').defaultBaseUrl !== childView.get('enteredUrl')) { - var selectedStack = self.get('controller.content.stacks').findProperty('isSelected', true); - var cos = selectedStack.operatingSystems.findProperty('osType', childView.get('selectedOS').osType); - cos.baseUrl = childView.get('enteredUrl'); - self.refreshRepositoryInfo(); - this.hide(); - } else { - error = Em.I18n.t('installer.step1.advancedRepo.localRepo.error.modifyUrl'); - } - } else { - error = Em.I18n.t('installer.step1.advancedRepo.localRepo.error.noUrl') - } - if (childView.get('isVisible')) - childView.set('errorMessage', error); - } - } - }, - onSecondary: function () { - this.hide(); - }, - bodyClass: Ember.View.extend({ - templateName: require('templates/wizard/step1_addLocalRepository'), - controller: self.get('controller'), - stackName: selectedStack.get('name'), - selectedOS: defaultRepos.objectAt(0), - enteredUrl: defaultRepos.objectAt(0).baseUrl, - oses: defaultRepos, - errorMessage: null, - selectOS: function (event) { - var os = event.context; - this.set('selectedOS', os); - this.set('enteredUrl', os.baseUrl); + if (selectedStack && selectedStack.operatingSystems) { + selectedStack.operatingSystems.forEach(function (os) { + var target = repos.findProperty('osType', os.osType); + if ( os.baseUrl != target.get('baseUrl')) { + os.baseUrl = target.get('baseUrl'); + os.validation = null; + target.set('undo', target.get('baseUrl') != target.get('defaultBaseUrl')); + target.set('invalid-error', false); + target.set('validation', null); + target.set('empty-error',!target.get('baseUrl')); } - }) - }); + }); + } + }.observes('[email protected]'), + isSubmitDisabled: function() { + return this.get('repoErrorCnt') != 0; + }.property('repoErrorCnt'), + invalidUrlExist: function () { + var selectedStack = this.get('controller.content.stacks').findProperty('isSelected', true); + return (selectedStack.get('invalidCnt') > 0); + }.property('[email protected]'), + + /** + * Onclick handler for Config Group Header. Used to show/hide block + */ + onToggleBlock: function () { + this.$('.accordion-body').toggle('blind', 500); + this.set('isRLCollapsed', !this.get('isRLCollapsed')); + }, + isRLCollapsed: true, + didInsertElement: function () { + if (this.get('isRLCollapsed')) { + this.$('.accordion-body').hide(); + } } });
