Przeglądaj źródła

AMBARI-3319. Simplify Local Repo setup via UI. (xiwang via yusaku)

Yusaku Sako 11 lat temu
rodzic
commit
82d26cbe05

+ 1 - 1
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

+ 70 - 0
ambari-web/app/controllers/installer.js

@@ -356,6 +356,76 @@ App.InstallerController = App.WizardController.extend({
     this.set('content.stacks', stacks);
   },
 
+  /**
+   * 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>
    */

+ 3 - 1
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.',

+ 17 - 4
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);
     }
   }),
 

+ 48 - 15
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;
+        }
       }
     }
-    
   }
+
 }

+ 49 - 30
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}}>&larr; {{t common.back}}</a>
-<a class="btn btn-success pull-right" {{action next}}>{{t common.next}} &rarr;</a>
+{{#if view.isSubmitDisabled}}
+   <a class="btn btn-success pull-right" disabled="disabled">{{t common.next}} &rarr;</a>
+{{else}}
+   <a class="btn btn-success pull-right" {{action next}}>{{t common.next}} &rarr;</a>
+{{/if}}

+ 1 - 3
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}}

+ 12 - 0
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': '',

+ 63 - 77
ambari-web/app/views/wizard/step1_view.js

@@ -31,27 +31,6 @@ App.WizardStep1View = Em.View.extend({
     return stacks;
   }.property('controller.content.stacks.@each.isSelected'),
 
-  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('controller.content.stacks.@each.isSelected', 'controller.content.stacks.@each.operatingSystems.@each.baseUrl'),
-
   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('allRepositories.@each.empty-error'),
+  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('controller.content.stacks.@each.isSelected', 'controller.content.stacks.@each.reload'),
 
+  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('allRepositories.@each.baseUrl'),
+  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('controller.content.stacks.@each.invalidCnt'),
+
+  /**
+   * 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();
+    }
   }
 });