You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by xi...@apache.org on 2014/05/19 20:58:55 UTC

git commit: AMBARI-5715. Usability: Ability to edit local repo urls in the UI - post install.(xiwang)

Repository: ambari
Updated Branches:
  refs/heads/trunk 742f5af65 -> 8b1d76cdf


AMBARI-5715. Usability: Ability to edit local repo urls in the UI - post install.(xiwang)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8b1d76cd
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8b1d76cd
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8b1d76cd

Branch: refs/heads/trunk
Commit: 8b1d76cdf4e0d8e54ba157411d997a4e1e00bc3d
Parents: 742f5af
Author: Xi Wang <xi...@apache.org>
Authored: Fri May 16 11:35:25 2014 -0700
Committer: Xi Wang <xi...@apache.org>
Committed: Mon May 19 11:50:47 2014 -0700

----------------------------------------------------------------------
 .../app/controllers/main/admin/cluster.js       |   7 +-
 ambari-web/app/messages.js                      |   9 +
 ambari-web/app/styles/application.less          | 114 ++++++++++
 ambari-web/app/templates/common/modal_popup.hbs |   3 +
 ambari-web/app/templates/main/admin/cluster.hbs |  80 +++++--
 ambari-web/app/views/common/modal_popup.js      |   6 +
 ambari-web/app/views/main/admin/cluster.js      | 218 ++++++++++++++++++-
 7 files changed, 411 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/controllers/main/admin/cluster.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/cluster.js b/ambari-web/app/controllers/main/admin/cluster.js
index 7a348b1..6e105da 100644
--- a/ambari-web/app/controllers/main/admin/cluster.js
+++ b/ambari-web/app/controllers/main/admin/cluster.js
@@ -22,7 +22,7 @@ var stringUtils = require('utils/string_utils');
 App.MainAdminClusterController = Em.Controller.extend({
   name:'mainAdminClusterController',
   services: [],
-  repositories: [],
+  allRepos: [],
   upgradeVersion: '',
   /**
    * get the newest version of HDP from server
@@ -98,6 +98,9 @@ App.MainAdminClusterController = Em.Controller.extend({
           baseUrl: repository.Repositories.base_url,
           osType: osType,
           repoId: repository.Repositories.repo_id,
+          repoName : repository.Repositories.repo_name,
+          stackName : repository.Repositories.stack_name,
+          stackVersion : repository.Repositories.stack_version,
           isFirst: false
         });
         var group = allRepos.findProperty('name', osType);
@@ -113,7 +116,7 @@ App.MainAdminClusterController = Em.Controller.extend({
       });
     }, this);
     allRepos.stackVersion = App.get('currentStackVersionNumber');
-    this.set('repositories', allRepos);
+    this.set('allRepos', allRepos);
   },
 
   loadRepositoriesErrorCallback: function(request, ajaxOptions, error) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index ca273c7..7df4930 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -116,6 +116,7 @@ Em.I18n.translations = {
   'common.component':'Component',
   'common.quickLinks':'Quick Links',
   'common.save':'Save',
+  'common.saveAnyway':'Save Anyway',
   'common.servers':'Servers',
   'common.clients':'Clients',
   'common.user': 'User',
@@ -163,6 +164,7 @@ Em.I18n.translations = {
   'common.startTime': 'Start Time',
   'common.duration': 'Duration',
   'common.reinstall': 'Re-Install',
+  'common.revert': 'Revert',
   'common.errorPopup.header': 'An error has been encountered',
   'common.use': 'Use',
   'common.stacks': 'Stacks',
@@ -1024,6 +1026,13 @@ Em.I18n.translations = {
   'admin.cluster.repositories.repositories':'Repositories',
   'admin.cluster.repositories.os':'OS',
   'admin.cluster.repositories.baseUrl':'Base URL',
+  'admin.cluster.repositories.popup.header.success':'Repo Base URLs Saved',
+  'admin.cluster.repositories.popup.header.fail':'Base URL Validation Failed',
+  'admin.cluster.repositories.popup.body.success':'The Repo Base URL has been saved successfully.',
+  'admin.cluster.repositories.popup.body.fail':'The Base URL failed validation and has not been saved.',
+  'admin.cluster.repositories.invalidURLAttention': '<b>Attention:</b> Please make sure all repository URLs are valid before saving.',
+  'admin.cluster.repositories.emptyURLattention':'<b>Attention:</b> Repository URLs are REQUIRED before you can save.',
+  'admin.cluster.repositories.skipValidation.tooltip':'<b>Warning:</b> This is for advanced users only. Use this option if you want to skip validation for Repository Base URLs.',
 
   'admin.misc.header': 'Service Users and Groups',
   'admin.misc.nothingToShow': 'No user accounts to display',

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index a251225..aac13a4 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -5853,6 +5853,8 @@ i.icon-asterisks {
   }
   .accordion-body {
     .repositories-table {
+      overflow: auto;
+      margin-bottom: 10px;
       div {
         float: left;
         min-height: 1px;
@@ -5954,6 +5956,118 @@ i.icon-asterisks {
   }
 }
 
+.admin-cluster {
+  .repositories-table {
+    margin-bottom: 10px;
+    border: 1px solid #dddddd;
+    overflow: auto;
+    div {
+      float: left;
+      min-height: 1px;
+    }
+    .thead {
+      width: 100%;
+      .th {
+        font-weight: bold;
+        padding: 8px;
+      }
+      .os-th {
+        width: 10%;
+      }
+      .name-th {
+        width: 16%;
+      }
+      .url-th {
+        width: 66%;
+      }
+    }
+    .tbody {
+      width: 100%;
+      .trow {
+        width: 100%;
+        border-top: 1px solid #dddddd;
+        padding-top: 8px;
+        .os-td {
+          padding-top: 4px;
+          padding-left: 8px;
+          width: 9%;
+        }
+        .sub-trow {
+          width: 100%;
+          min-height: 39px;
+          .name-td {
+            width: 16%;
+            padding-top: 4px;
+          }
+          .url-td {
+            width: 60%;
+            .ember-text-field {
+              width: 100%;
+            }
+          }
+          .url-text-td {
+            width: 70%;
+            padding-top: 4px;
+            padding-left: 3px;
+            overflow: scroll;
+          }
+          .edit-td {
+            width: 8%;
+            padding-top: 4px;
+            padding-left: 5px;
+            a {
+              cursor: pointer;
+            }
+          }
+          .edit-buttons-td {
+            // save or cancel
+            width: 9%;
+          }
+          .clear-td {
+            width: 3%;
+            padding-top: 5px;
+            padding-left: 12px;
+            a {
+              cursor: pointer;
+              text-decoration: none;
+            }
+            .icon-remove-sign {
+              color: #808080;
+            }
+          }
+        }
+      }
+    }
+    .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);
+    }
+    .disabled-textfield input {
+      color: #808080;
+      disabled: disabled;
+      pointer-events: none;
+      cursor: default;
+      background: #E4E4E4;
+    }
+    .disabled-label {
+      color: #808080;
+    }
+  }
+  #skip-validation {
+    margin-top: 10px;
+    .checkbox {
+     margin-left: 3px;
+     margin-right: 8px;
+     margin-top: 0px;
+    }
+    .icon-question-sign {
+     color: @blue;
+    }
+  }
+}
+
 #config-group-select-create-dialog {
   .select-create-config-group-div {
     margin-left: 20px;

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/templates/common/modal_popup.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/modal_popup.hbs b/ambari-web/app/templates/common/modal_popup.hbs
index 65a95d7..c2326d4 100644
--- a/ambari-web/app/templates/common/modal_popup.hbs
+++ b/ambari-web/app/templates/common/modal_popup.hbs
@@ -51,6 +51,9 @@
           <label id="footer-checkbox">{{view Ember.Checkbox classNames="checkbox" checkedBinding="view.isNotShowBgChecked"}} &nbsp;
           {{t app.settings.notShowBgOperations}}</label>
         {{/if}}
+        {{#if view.third}}
+          <button class="btn" {{bindAttr disabled="view.disableThird"}} {{action onThird target="view"}}>{{view.third}}</button>
+        {{/if}}
         {{#if view.secondary}}
           <button class="btn" {{bindAttr disabled="view.disableSecondary"}} {{action onSecondary target="view"}}>{{view.secondary}}</button>
         {{/if}}

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/templates/main/admin/cluster.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/admin/cluster.hbs b/ambari-web/app/templates/main/admin/cluster.hbs
index 771b261..b38b28c 100644
--- a/ambari-web/app/templates/main/admin/cluster.hbs
+++ b/ambari-web/app/templates/main/admin/cluster.hbs
@@ -56,30 +56,66 @@
     </div>
     <ul class="nav nav-tabs">
       <li class="active">
-        <a href="javascript:void(null);">{{repositories.stackVersion}}</a>
+        <a href="javascript:void(null);">{{view.allRepositoriesGroups.stackVersion}}</a>
       </li>
     </ul>
 
-    <table class="table table-bordered">
-      <thead>
-      <tr>
-        <th>{{t admin.cluster.repositories.os}}</th>
-        <th>{{t common.name}}</th>
-        <th>{{t admin.cluster.repositories.baseUrl}}</th>
-      </tr>
-      </thead>
-      <tbody>
-        {{#each repoGroup in repositories}}
-          {{#each repo in repoGroup.repositories}}
-            <tr>
-              {{#if repo.isFirst}}
-                <td {{bindAttr rowspan="repoGroup.repositories.length"}}>{{repo.osType}}</td>
-              {{/if}}
-              <td>{{repo.repoId}}</td>
-              <td>{{repo.baseUrl}}</td>
-            </tr>
-          {{/each}}
+    <div class="repositories-table">
+      <div class="thead">
+        <div class="th os-th">{{t common.os}}</div>
+        <div class="th name-th">{{t common.name}}</div>
+        <div class="th url-th">{{t installer.step1.advancedRepo.localRepo.column.baseUrl}}</div>
+      </div>
+      <div class="tbody">
+        {{#each repoGroup in view.allRepositoriesGroups}}
+          <div class="trow">
+            <div class="os-td">
+              <label>
+                <span class="os">{{repoGroup.name}}</span>
+              </label>
+            </div>
+              <div style="width:89%">
+                {{#each repository in repoGroup.repositories}}
+                  <div class="sub-trow">
+                    <div class="name-td">{{repository.repoId}}</div>
+                    <!--edit mode for current url-->
+                    {{#if repository.onEdit}}
+                      <div {{bindAttr class=":url-td repository.empty-error:textfield-error repository.invalid-error:textfield-error"}}>
+                        {{view Ember.TextField valueBinding="repository.baseUrl"}}
+                      </div>
+                      <div class="clear-td">
+                        {{#if repository.clearAll}}
+                          <a {{action "clearGroupLocalRepository" repository target="view" }}>
+                            <i class="icon-remove-sign"></i>
+                          </a>
+                        {{/if}}
+                      </div>
+                      <div class="edit-buttons-td">
+                        <a class="btn" {{action doCancel repository target="view"}}>{{t common.cancel}}</a>
+                      </div>
+                        <div class="edit-buttons-td">
+                          {{#if repository.empty-error}}
+                            <a class="btn btn-primary" disabled="disabled">{{t common.save}}</a>
+                          {{else}}
+                            <a class="btn btn-primary" {{action saveRepoUrls repository target="view"}}>{{t common.save}}</a>
+                          {{/if}}
+                        </div>
+                    <!--non-edit mode for current url-->
+                    {{else}}
+                      <div class="url-text-td">
+                        {{repository.baseUrl}}
+                      </div>
+                      <div class="edit-td">
+                        <a {{action "onEditClick" repository target="view" }}>
+                          <i class="icon-edit"></i> {{t common.edit}}
+                        </a>
+                      </div>
+                    {{/if}}
+                  </div>
+                {{/each}}
+              </div>
+          </div>
         {{/each}}
-      </tbody>
-    </table>
+      </div>
+    </div>
 </div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/views/common/modal_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/modal_popup.js b/ambari-web/app/views/common/modal_popup.js
index 9be6050..550af3c 100644
--- a/ambari-web/app/views/common/modal_popup.js
+++ b/ambari-web/app/views/common/modal_popup.js
@@ -28,9 +28,11 @@ App.ModalPopup = Ember.View.extend({
   // define bodyClass which extends Ember.View to use an arbitrary Handlebars template as the body
   primary: Em.I18n.t('ok'),
   secondary: Em.I18n.t('common.cancel'),
+  third: null,
   autoHeight: true,
   disablePrimary: false,
   disableSecondary: false,
+  disableThird: false,
   primaryClass: 'btn-success',
   onPrimary: function () {
     this.hide();
@@ -40,6 +42,10 @@ App.ModalPopup = Ember.View.extend({
     this.hide();
   },
 
+  onThird: function () {
+    this.hide();
+  },
+
   onClose: function () {
     this.hide();
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/views/main/admin/cluster.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/admin/cluster.js b/ambari-web/app/views/main/admin/cluster.js
index 3c26f03..44ae19b 100644
--- a/ambari-web/app/views/main/admin/cluster.js
+++ b/ambari-web/app/views/main/admin/cluster.js
@@ -24,5 +24,219 @@ App.MainAdminClusterView = Em.View.extend({
 
   isUpgradeAvailable: function(){
     return stringUtils.compareVersions(this.get('controller.upgradeVersion').replace(/HDP(Local)?-/, ''), App.get('currentStackVersionNumber')) === 1;
-  }.property('controller.upgradeVersion', 'App.currentStackVersion')
-});
\ No newline at end of file
+  }.property('controller.upgradeVersion', 'App.currentStackVersion'),
+
+  didInsertElement: function () {
+    this.get('controller').loadRepositories();
+  },
+
+  /**
+   * List of all repo-groups
+   * @type {Object[][]}
+   */
+  allRepositoriesGroups: function () {
+    var repos = this.get('controller.allRepos');
+    var reposGroup = [];
+    var repositories = [];
+    reposGroup.set('stackVersion', App.get('currentStackVersionNumber'));
+    if (repos) {
+      repos.forEach(function (group) {
+        group.repositories.forEach (function(repo) {
+          var cur_repo = Em.Object.create({
+            'repoId': repo.repoId,
+            'id': repo.repoId + '-' + repo.osType,
+            'repoName' : repo.repoName,
+            'stackName' : repo.stackName,
+            'stackVersion' : repo.stackVersion,
+            'baseUrl': repo.baseUrl,
+            'originalBaseUrl': repo.baseUrl,
+            'osType': repo.osType,
+            'onEdit': false,
+            'empty-error': !repo.baseUrl,
+            'undo': false,
+            'clearAll': repo.baseUrl
+          });
+          var cur_group = reposGroup.findProperty('name', group.name);
+          if (!cur_group) {
+            var cur_group = Ember.Object.create({
+              name: group.name,
+              repositories: []
+            });
+            reposGroup.push(cur_group);
+          }
+          cur_group.repositories.push(cur_repo);
+          repositories.push(cur_repo);
+        });
+      });
+    }
+    this.set('allRepos', repositories);
+    return reposGroup;
+  }.property('controller.allRepos'),
+
+  /**
+   * Onclick handler for edit action of each repo, enter edit mode
+   * @param {object} event
+   */
+  onEditClick:function (event) {
+    var targetRepo = this.get('allRepos').findProperty('id', event.context.get('id'));
+    if (targetRepo) {
+      targetRepo.set('onEdit', true);
+    }
+  },
+
+  /**
+   * Onclick handler for undo action of each repo group
+   * @method undoGroupLocalRepository
+   * @param {object} event
+   */
+  undoGroupLocalRepository: function (event) {
+    this.doActionForGroupLocalRepository(event, 'originalBaseUrl');
+  },
+
+  /**
+   * Handler for clear icon click
+   * @method clearGroupLocalRepository
+   * @param {object} event
+   */
+  clearGroupLocalRepository: function (event) {
+    this.doActionForGroupLocalRepository(event, '');
+  },
+
+  /**
+   * Common handler for repo groups actions
+   * @method doActionForGroupLocalRepository
+   * @param {object} event
+   * @param {string} newBaseUrlField
+   */
+  doActionForGroupLocalRepository: function (event, newBaseUrlField) {
+    var targetRepo = this.get('allRepos').findProperty('id', event.context.get('id'));
+    if (targetRepo) {
+      targetRepo.set('baseUrl', Em.isEmpty(newBaseUrlField) ? '' : Em.get(targetRepo, newBaseUrlField));
+    }
+  },
+
+  /**
+   * Handler when editing any repo group BaseUrl
+   * @method editGroupLocalRepository
+   */
+  editGroupLocalRepository: function (event) {
+    var repos = this.get('allRepos');
+    repos.forEach(function (targetRepo) {
+      targetRepo.set('undo', targetRepo.get('baseUrl') != targetRepo.get('originalBaseUrl'));
+      targetRepo.set('clearAll', targetRepo.get('baseUrl'));
+      targetRepo.set('empty-error', !targetRepo.get('baseUrl'));
+
+    });
+  }.observes('allRepos.@each.baseUrl'),
+
+  /**
+   * onSuccess callback for save Repo URL.
+   */
+  doSaveRepoUrlsSuccessCallback: function (response, request, data) {
+    var id = data.repoId + '-' + data.osType;
+    console.log('Success in check Repo URL. data repoId+osType: ' + id);
+    var targetRepo = this.get('allRepos').findProperty('id', id);
+    if (!targetRepo) {
+      return;
+    } else {
+      App.ModalPopup.show({
+        header: Em.I18n.t('admin.cluster.repositories.popup.header.success'),
+        secondary: null,
+        onPrimary: function () {
+          this.hide();
+          targetRepo.set('baseUrl', data.data.Repositories.base_url);
+          targetRepo.set('originalBaseUrl', data.data.Repositories.base_url);
+          targetRepo.set('onEdit', false);
+        },
+        message: Em.I18n.t('admin.cluster.repositories.popup.body.success'),
+        bodyClass: Em.View.extend({
+          template: Em.Handlebars.compile('<div class="alert alert-success">{{{message}}}</div>')
+        })
+      })
+    }
+  },
+
+  /**
+   * onError callback for save Repo URL.
+   */
+  doSaveRepoUrlsErrorCallback: function (request, ajaxOptions, error, data) {
+    console.log('Error in check Repo URL. The baseURL sent is:  ' + data.data);
+    var self = this;
+    var id = data.url.split('/')[10] + '-' + data.url.split('/')[8];
+    var targetRepo = this.get('allRepos').findProperty('id', id);
+    if (!targetRepo) {
+      return;
+    } else {
+      App.ModalPopup.show({
+        header: Em.I18n.t('admin.cluster.repositories.popup.header.fail'),
+        primary: Em.I18n.t('common.saveAnyway'),
+        secondary: Em.I18n.t('common.revert'),
+        third: Em.I18n.t('common.cancel'),
+        onPrimary: function () {
+          // save anyway: Go ahead and save with Repo URL validation turned off and close Dialog when done.
+          this.hide();
+          self.doSaveRepoUrls(id, false);
+        },
+        onSecondary: function () {
+          // Revert: Close dialog, revert URL value, go back to non-Edit mode
+          this.hide();
+          targetRepo.set('baseUrl', targetRepo.get('originalBaseUrl'));
+          targetRepo.set('onEdit', false);
+        },
+        onThird: function () {
+          // cancel: Close dialog but stay in Edit mode
+          this.hide();
+        },
+        message: Em.I18n.t('admin.cluster.repositories.popup.body.fail'),
+        bodyClass: Em.View.extend({
+          template: Em.Handlebars.compile('<div class="alert alert-warning">{{{message}}}</div>')
+        })
+      })
+    }
+  },
+
+  /**
+   * Check validation and Save the customized local urls
+   */
+  doSaveRepoUrls: function (id, verifyBaseUrl) {
+    var targetRepo = this.get('allRepos').findProperty('id', id);
+    var verifyBaseUrl = verifyBaseUrl;
+    App.ajax.send({
+      name: 'wizard.advanced_repositories.valid_url',
+      sender: this,
+      data: {
+        stackName: targetRepo.stackName,
+        stackVersion: targetRepo.stackVersion,
+        repoId: targetRepo.repoId,
+        osType: targetRepo.osType,
+        data: {
+          'Repositories': {
+            'base_url': targetRepo.baseUrl,
+            "verify_base_url": verifyBaseUrl
+          }
+        }
+      },
+      success: 'doSaveRepoUrlsSuccessCallback',
+      error: 'doSaveRepoUrlsErrorCallback'
+    });
+  },
+  /**
+   * Check validation and Save the customized local urls
+   */
+  saveRepoUrls: function (event) {
+    this.doSaveRepoUrls(event.context.get('id'), true);
+  },
+
+  /**
+   * on click handler 'Cancel' for current repo in edit mode
+   */
+  doCancel: function (event) {
+    var targetRepo = this.get('allRepos').findProperty('id', event.context.get('id'));
+    if (targetRepo) {
+      targetRepo.set('baseUrl', targetRepo.get('originalBaseUrl'));
+      targetRepo.set('onEdit', false);
+    }
+  }
+
+});
+