You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2015/10/16 12:38:41 UTC

[1/2] ambari git commit: AMBARI-13449. FE changes for persisting/updating/removing KDC admin credentials.

Repository: ambari
Updated Branches:
  refs/heads/trunk a509510ee -> 1076ec760


AMBARI-13449. FE changes for persisting/updating/removing KDC admin credentials.


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

Branch: refs/heads/trunk
Commit: 1076ec760bdafae4de876e66e39acad4f26c0b7e
Parents: 4a95428
Author: Alex Antonenko <hi...@gmail.com>
Authored: Fri Oct 16 13:13:53 2015 +0300
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Fri Oct 16 13:38:21 2015 +0300

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   1 +
 .../app/controllers/main/admin/kerberos.js      |   5 +
 .../main/admin/kerberos/wizard_controller.js    |  10 +-
 ambari-web/app/messages.js                      |   3 +
 .../common/kdc_credentials_controller_mixin.js  |   5 +-
 ambari-web/app/styles/common.less               |   8 +-
 .../common/form/manage_credentilas_form.hbs     |  58 +++++
 .../app/templates/main/admin/kerberos.hbs       |   3 +
 ambari-web/app/utils/ajax/ajax.js               |   2 +-
 ambari-web/app/utils/credentials.js             |  47 ++++-
 ambari-web/app/views.js                         |   2 +
 .../common/form/manage_credentials_form_view.js | 209 +++++++++++++++++++
 .../common/modal_popups/invalid_KDC_popup.js    |   2 +-
 .../manage_kdc_credentials_popup.js             |  58 +++++
 .../form/manage_kdc_credentials_form_test.js    | 138 ++++++++++++
 15 files changed, 535 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index 5b5f588..b59f91f 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -273,6 +273,7 @@ var files = [
   'test/views/common/controls_view_test',
   'test/views/common/configs/widgets/time_interval_spinner_view_test',
   'test/views/common/form/spinner_input_view_test',
+  'test/views/common/form/manage_kdc_credentials_form_test',
   'test/views/wizard/step3/hostLogPopupBody_view_test',
   'test/views/wizard/step3/hostWarningPopupBody_view_test',
   'test/views/wizard/step3/hostWarningPopupFooter_view_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/controllers/main/admin/kerberos.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/kerberos.js b/ambari-web/app/controllers/main/admin/kerberos.js
index ca2f0dc..d339f63 100644
--- a/ambari-web/app/controllers/main/admin/kerberos.js
+++ b/ambari-web/app/controllers/main/admin/kerberos.js
@@ -582,5 +582,10 @@ App.MainAdminKerberosController = App.KerberosWizardStep4Controller.extend({
         dfd.reject();
       }, Em.I18n.t('common.warning'), Em.I18n.t('common.proceedAnyway'));
     }
+  },
+
+  showManageKDCCredentialsPopup: function() {
+    return App.showManageCredentialsPopup();
   }
+
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js b/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
index 6209f29..e0e146a 100644
--- a/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
+++ b/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
@@ -263,10 +263,14 @@ App.KerberosWizardController = App.WizardController.extend(App.InstallComponent,
         callback: function() {
           var self = this;
           var dfd = $.Deferred();
-          credentialsUtils.isStorePersisted(App.get('clusterName')).then(function(isPersisted) {
-            self.set('content.secureStoragePersisted', isPersisted);
+          if (App.get('supports.storeKDCCredentials')) {
+            credentialsUtils.isStorePersisted(App.get('clusterName')).then(function(isPersisted) {
+              self.set('content.secureStoragePersisted', isPersisted);
+              dfd.resolve();
+            });
+          } else {
             dfd.resolve();
-          });
+          }
           return dfd.promise();
         }
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 6437ee9..5df161b 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -1033,6 +1033,9 @@ Em.I18n.translations = {
   'admin.kerberos.credentials.store.hint.supported': 'When checked, Ambari will store the KDC Admin credentials so they are not required to be re-entered during future changes of services, hosts, and components.',
   'admin.kerberos.credentials.store.hint.not.supported': 'Ambari is not configured for storing credentials',
   'admin.kerberos.credentials.store.label': 'Save Admin Credentials',
+  'admin.kerberos.credentials.store.menu.label': 'Manage KDC Credentials',
+  'admin.kerberos.credentials.remove.confirmation.header': 'Remove KDC Credentials Confirmation',
+  'admin.kerberos.credentials.remove.confirmation.body': 'You are about to remove the KDC Credentials from Ambari. Are you sure?',
   'admin.kerberos.wizard.configuration.note': 'This is the initial configuration created by Enable Kerberos wizard.',
   'admin.kerberos.wizard.header':'Enable Kerberos Wizard',
   'admin.kerberos.button.enable': 'Enable Kerberos',

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js b/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
index 67f2664..070ea35 100644
--- a/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
+++ b/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
@@ -81,14 +81,11 @@ App.KDCCredentialsControllerMixin = Em.Mixin.create({
    * @returns {$.Deferred} promise object
    */
   createKDCCredentials: function(configs) {
-    var self = this;
     var resource = credentialsUtils.createCredentialResource(
       configs.findProperty('name', 'admin_principal').get('value'),
       configs.findProperty('name', 'admin_password').get('value'),
       this._getStorageTypeValue(configs));
-    return credentialsUtils.createCredentials(App.get('clusterName'), this.get('credentialAlias'), resource).fail(function() {
-      return self.updateKDCCredentials(resource);
-    });
+    return credentialsUtils.createOrUpdateCredentials(App.get('clusterName'), this.get('credentialAlias'), resource);
   },
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/styles/common.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/common.less b/ambari-web/app/styles/common.less
index b4cb61d..60b7553 100644
--- a/ambari-web/app/styles/common.less
+++ b/ambari-web/app/styles/common.less
@@ -224,8 +224,8 @@
   text-overflow: ellipsis;
   white-space: nowrap;
   position: relative;
-  padding: 0px 0px;
-  margin: 0px 0px;
+  padding: 0 0;
+  margin: 0 0;
   border: none;
   width: 50px;
   height: 18px;
@@ -365,4 +365,8 @@
       outline: 0 none;
     }
   }
+}
+
+.lh-btn {
+  line-height: 30px;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/templates/common/form/manage_credentilas_form.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/form/manage_credentilas_form.hbs b/ambari-web/app/templates/common/form/manage_credentilas_form.hbs
new file mode 100644
index 0000000..c72e024
--- /dev/null
+++ b/ambari-web/app/templates/common/form/manage_credentilas_form.hbs
@@ -0,0 +1,58 @@
+{{!
+* 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.
+}}
+
+
+<form class="form-horizontal">
+  <div class="control-group">
+    <label class="control-label">{{t popup.invalid.KDC.admin.principal}}</label>
+    <div class="controls">
+      {{view Ember.TextField valueBinding="view.principal" class="form-control"}}
+    </div>
+  </div>
+  <div class="control-group">
+    <label class="control-label">{{t popup.invalid.KDC.admin.password}}</label>
+    <div class="controls">
+      {{view Ember.TextField type="password" valueBinding="view.password" class="form-control"}}
+    </div>
+  </div>
+  <div class="control-group">
+    <span class="control-label"></span>
+    <div class="controls">
+      {{#if App.supports.storeKDCCredentials}}
+        <label>
+          {{view Ember.Checkbox checkedBinding="view.storeCredentials" disabledBinding="view.checkboxDisabled" classNames="pull-left"}}
+          <span {{bindAttr class=":mls view.checkboxDisabled:muted"}}>
+            {{t admin.kerberos.credentials.store.label}}
+            <a class="icon-question-sign icon-blue" rel="tooltip" href="javascript:void(null);" data-toggle="tooltip" {{bindAttr data-original-title="view.hintMessage"}}><a/>
+          </span>
+        </label>
+      {{/if}}
+    </div>
+  </div>
+  <div class="control-group">
+    <span class="control-label"></span>
+    <div class="controls">
+      <button {{bindAttr class=":btn :btn-danger :pull-left view.isRemovable::hidden" disabled="view.isRemoveDisabled"}} {{action removeKDCCredentials target="view"}}>
+        <i class="icon-remove-circle"></i> {{t common.remove}}</button>
+      <div {{bindAttr class=":spinner :mll :pull-left view.isActionInProgress::hide"}}></div>
+      {{#if view.actionStatus}}
+        <span class="pull-left lh-btn mll text-success">{{view.actionStatus}}</span>
+      {{/if}}
+    </div>
+  </div>
+</form>

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/templates/main/admin/kerberos.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/admin/kerberos.hbs b/ambari-web/app/templates/main/admin/kerberos.hbs
index 88aa4e2..2ba6001 100644
--- a/ambari-web/app/templates/main/admin/kerberos.hbs
+++ b/ambari-web/app/templates/main/admin/kerberos.hbs
@@ -24,6 +24,9 @@
           {{#unless isManualKerberos}}
             <button class="btn btn-success" {{bindAttr disabled="isKerberosButtonsDisabled"}} {{action regenerateKeytabs target="controller"}}>
               <i class="icon-repeat"></i> {{t admin.kerberos.button.regenerateKeytabs}}</button>
+            {{#if App.supports.storeKDCCredentials}}
+              <button class="btn btn-primary" {{action showManageKDCCredentialsPopup target="controller"}}>{{t admin.kerberos.credentials.store.menu.label}}</button>
+            {{/if}}
           {{/unless}}
           <br/>
           {{#unless isEditMode}}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/utils/ajax/ajax.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js
index f0ebbff..52f3aca 100644
--- a/ambari-web/app/utils/ajax/ajax.js
+++ b/ambari-web/app/utils/ajax/ajax.js
@@ -797,7 +797,7 @@ var urls = {
   },
 
   'credentials.list': {
-    'real': '/clusters/{clusterName}/credentials',
+    'real': '/clusters/{clusterName}/credentials?fields=Credential/*',
     'mock': ''
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/utils/credentials.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/credentials.js b/ambari-web/app/utils/credentials.js
index 0639091..cc45288 100644
--- a/ambari-web/app/utils/credentials.js
+++ b/ambari-web/app/utils/credentials.js
@@ -69,11 +69,37 @@ module.exports = {
     });
   },
 
+  credentialsSuccessCallback: function(data, opt, params) {
+    params.callback(data.items.length ? data.items.mapProperty('Credential') : []);
+  },
+
   createCredentialsErrorCallback: function(req, ajaxOpts, error) {
     console.error('createCredentials ERROR:', error);
   },
 
   /**
+   * @see createCredentials
+   * @param {string} clusterName
+   * @param {string} alias
+   * @param {object} resource
+   * @returns {$.Deferred} promise object
+   */
+  createOrUpdateCredentials: function(clusterName, alias, resource) {
+    var self = this;
+    var dfd = $.Deferred();
+    this.createCredentials(clusterName, alias, resource).then(function() {
+      dfd.resolve();
+    }, function() {
+      self.updateCredentials(clusterName, alias, resource).always(function() {
+        var status = arguments[1];
+        var result = arguments[2];
+        dfd.resolve(status === "success", result);
+      });
+    });
+    return dfd.promise();
+  },
+
+  /**
    * Retrieve single credential from cluster by specified alias name
    *
    * @member utils.credentials
@@ -131,16 +157,13 @@ module.exports = {
       sender: this,
       name: 'credentials.list',
       data: {
-        clusterName: clusterName
+        clusterName: clusterName,
+        callback: callback
       },
       success: 'credentialsSuccessCallback'
     });
   },
 
-  credentialsSuccessCallback: function(data, opt, params) {
-    params.callback(data.items.length ? data.items.mapProperty('Credential') : []);
-  },
-
   /**
    * Remove credential from server by specified cluster name and alias
    *
@@ -241,5 +264,19 @@ module.exports = {
       key: key,
       type: type
     };
+  },
+
+  /**
+   * Check that KDC credentials stored as <b>persisted</b> and not <b>temporary</b> from specified credentials list.
+   *
+   * @param {object[]} credentials credentials list retrieved from API @see credentials
+   * @returns {boolean} <code>true</code> if credentials are persisted
+   */
+  isKDCCredentialsPersisted: function(credentials) {
+    var kdcCredentials = credentials.findProperty('alias', this.ALIAS.KDC_CREDENTIALS);
+    if (kdcCredentials) {
+      return Em.getWithDefault(kdcCredentials, 'type', this.STORE_TYPES.TEMPORARY) === this.STORE_TYPES.PERSISTENT;
+    }
+    return false;
   }
 };

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index e83b5d2..d6132e6 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -27,6 +27,7 @@ require('views/common/chart/linear');
 require('views/common/chart/linear_time');
 require('views/common/modal_popup');
 require('views/common/modal_popups/alert_popup');
+require('views/common/modal_popups/manage_kdc_credentials_popup');
 require('views/common/modal_popups/confirmation_feedback_popup');
 require('views/common/modal_popups/confirmation_popup');
 require('views/common/modal_popups/hosts_table_list_popup');
@@ -42,6 +43,7 @@ require('views/common/time_range');
 require('views/common/time_range_list');
 require('views/common/form/field');
 require('views/common/form/spinner_input_view');
+require('views/common/form/manage_credentials_form_view');
 require('views/common/quick_view_link_view');
 require('views/common/configs/services_config');
 require('views/common/configs/service_config_container_view');

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/views/common/form/manage_credentials_form_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/form/manage_credentials_form_view.js b/ambari-web/app/views/common/form/manage_credentials_form_view.js
new file mode 100644
index 0000000..c71021c
--- /dev/null
+++ b/ambari-web/app/views/common/form/manage_credentials_form_view.js
@@ -0,0 +1,209 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+var credentialsUtils = require('utils/credentials');
+
+App.ManageCredentialsFormView = Em.View.extend({
+  templateName: require('templates/common/form/manage_credentilas_form'),
+  viewName: 'manageCredentialsForm',
+  principal: "",
+  password: "",
+
+  /**
+   * Store Admin credentials checkbox value
+   *
+   * @type {boolean}
+   */
+  storeCredentials: false,
+
+  /**
+   * Status of persistent storage. Returns <code>true</code> if persistent storage is available.
+   * @type {boolean}
+   */
+  storePersisted: false,
+
+  /**
+   * Disable checkbox if persistent storage not available
+   *
+   * @type {boolean}
+   */
+  checkboxDisabled: Ember.computed.not('storePersisted'),
+
+  /**
+   * Credentials can be removed, in case when they stored to persistent secure storage.
+   *
+   * @type {boolean}
+   */
+  isRemovable: false,
+
+  /**
+   * Remove button disabled status.
+   *
+   * @type {boolean}
+   */
+  isRemoveDisabled: true,
+
+  /**
+   * Signalize that action was performed and waiting for result.
+   *
+   * @type {boolean}
+   */
+  isActionInProgress: false,
+
+  /**
+   * Status message of performed action, e.g. remove or save credentials. Set to <code>false</code> to hide this message.
+   *
+   * @type {boolean|string}
+   */
+  actionStatus: false,
+
+  isSubmitDisabled: function() {
+    return Em.isEmpty(this.get('principal')) || Em.isEmpty(this.get('password'));
+  }.property('principal', 'password'),
+
+  /**
+   * Returns storage type used to save credentials e.g. <b>persistent</b>, <b>temporary</b> (default)
+   *
+   * @type {string}
+   */
+  storageType: function() {
+    return this.get('storeCredentials') ? credentialsUtils.STORE_TYPES.PERSISTENT : credentialsUtils.STORE_TYPES.TEMPORARY;
+  }.property('storeCredentials'),
+
+  /**
+   * Message to display in tooltip regarding persistent storage state.
+   *
+   * @type {string}
+   */
+  hintMessage: function() {
+    return this.get('storePersisted') ?
+      Em.I18n.t('admin.kerberos.credentials.store.hint.supported') :
+      Em.I18n.t('admin.kerberos.credentials.store.hint.not.supported');
+  }.property('storePersisted'),
+
+  /**
+   * Observe changes for principal and password.
+   * Hide status message and toggle action progress if performed.
+   */
+  formInputObserver: function() {
+    if (this.get('actionStatus') || this.get('isActionInProgress')) {
+      this.setInProgress(false);
+      this.set('actionStatus', false);
+    }
+  }.observes('password', 'principal'),
+
+  didInsertElement: function() {
+    this._super();
+    App.tooltip(this.$('[rel="tooltip"]'));
+  },
+
+  willInsertElement: function() {
+    this._super();
+    this.prepareContent();
+  },
+
+  prepareContent: function() {
+    var self = this;
+    credentialsUtils.isStorePersisted(App.get('clusterName')).then(function(isPersisted) {
+      Em.run.next(function() {
+        self.set('storePersisted', isPersisted);
+      });
+    });
+    credentialsUtils.credentials(App.get('clusterName'), function(credentials) {
+      Em.run.next(function() {
+        self.set('isRemovable', credentialsUtils.isKDCCredentialsPersisted(credentials));
+        self.set('isRemoveDisabled', !self.get('isRemovable'));
+      });
+    });
+  },
+
+  /**
+   * Save credentials action.
+   *
+   * @returns {boolean|$.Deferred}
+   */
+  saveKDCCredentials: function () {
+    var self = this;
+    var dfd = $.Deferred();
+
+    this.setInProgress(true);
+    credentialsUtils.createOrUpdateCredentials(
+      App.get('clusterName'),
+      credentialsUtils.ALIAS.KDC_CREDENTIALS,
+      credentialsUtils.createCredentialResource(this.get('principal'), this.get('password'), this.get('storageType')))
+      .always(function() {
+        self.setInProgress(false);
+        self.prepareContent();
+        self.set('actionStatus', Em.I18n.t('common.success'));
+        self.get('parentView').set('isCredentialsSaved', true);
+        dfd.resolve();
+      });
+    return dfd.promise();
+  },
+
+  /**
+   * Remove KDC credentials action.
+   *
+   * @returns {App.ModalPopup}
+   */
+  removeKDCCredentials: function() {
+    var t = Em.I18n.t;
+    var self = this;
+    this.set('actionStatus', false);
+    var popup = App.showConfirmationPopup(
+      function() {
+        self.setInProgress(true);
+        credentialsUtils.removeCredentials(App.get('clusterName'), credentialsUtils.ALIAS.KDC_CREDENTIALS)
+          .always(function() {
+            self.setInProgress(false);
+            self.prepareContent();
+            self.set('actionStatus', Em.I18n.t('common.success'));
+            self.get('parentView').set('isCredentialsRemoved', true);
+          });
+      }, t('admin.kerberos.credentials.remove.confirmation.body'),
+      function () {},
+      null,
+      t('yes'),
+      false);
+    popup.set('secondary', t('no'));
+    return popup;
+  },
+
+  /**
+   * Toggle action status and disable/enable appropriate buttons.
+   *
+   * @param {boolean} [isInProgress=false] progress status
+   */
+  setInProgress: function(isInProgress) {
+    if (isInProgress) {
+      this.set('actionStatus', false);
+      if (this.get('isRemovable')) {
+        this.set('isRemoveDisabled', true);
+      }
+      this.set('isSubmitDisabled', true);
+      this.set('isActionInProgress', true);
+    } else {
+      if (this.get('isRemovable')) {
+        this.set('isRemoveDisabled', false);
+      }
+      this.set('isSubmitDisabled', false);
+      this.set('isActionInProgress', false);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/views/common/modal_popups/invalid_KDC_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/modal_popups/invalid_KDC_popup.js b/ambari-web/app/views/common/modal_popups/invalid_KDC_popup.js
index 9c63f8f..ca5de7d 100644
--- a/ambari-web/app/views/common/modal_popups/invalid_KDC_popup.js
+++ b/ambari-web/app/views/common/modal_popups/invalid_KDC_popup.js
@@ -110,7 +110,7 @@ App.showInvalidKDCPopup = function (ajaxOpt, message) {
       this.hide();
       if (App.get('supports.storeKDCCredentials')) {
         var resource = credentialsUtils.createCredentialResource(this.get('principal'), this.get('password'), this.get('storageType'));
-        credentialsUtils.updateCredentials(App.get('clusterName'), credentialsUtils.ALIAS.KDC_CREDENTIALS, resource);
+        credentialsUtils.createOrUpdateCredentials(App.get('clusterName'), credentialsUtils.ALIAS.KDC_CREDENTIALS, resource);
       }
       App.get('router.clusterController').createKerberosAdminSession(this.get('principal'), this.get('password'), ajaxOpt);
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/app/views/common/modal_popups/manage_kdc_credentials_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/modal_popups/manage_kdc_credentials_popup.js b/ambari-web/app/views/common/modal_popups/manage_kdc_credentials_popup.js
new file mode 100644
index 0000000..1de8e56
--- /dev/null
+++ b/ambari-web/app/views/common/modal_popups/manage_kdc_credentials_popup.js
@@ -0,0 +1,58 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+var credentialsUtils = require('utils/credentials');
+
+/**
+ * @return {*}
+ */
+App.showManageCredentialsPopup = function () {
+  return App.ModalPopup.show({
+    header: Em.I18n.t('admin.kerberos.credentials.store.menu.label'),
+    bodyClass: App.ManageCredentialsFormView,
+    primary: Em.I18n.t('common.save'),
+    isCredentialsRemoved: false,
+
+    disablePrimary: function() {
+      return this.get('formView.isSubmitDisabled');
+    }.property('formView.isSubmitDisabled'),
+
+    formView: function() {
+      return this.get('childViews').findProperty('viewName', 'manageCredentialsForm');
+    }.property(),
+
+    credentialsRemoveObserver: function() {
+      if (this.get('isCredentialsRemoved')) {
+        this.hide();
+      }
+    }.observes('isCredentialsRemoved'),
+
+    onPrimary: function() {
+      var self = this;
+      var formView = this.get('formView');
+      if (formView) {
+        formView.saveKDCCredentials().always(function() {
+          self.hide();
+        });
+      } else {
+        this.hide();
+      }
+    }
+  });
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/1076ec76/ambari-web/test/views/common/form/manage_kdc_credentials_form_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/form/manage_kdc_credentials_form_test.js b/ambari-web/test/views/common/form/manage_kdc_credentials_form_test.js
new file mode 100644
index 0000000..ca5e4d0
--- /dev/null
+++ b/ambari-web/test/views/common/form/manage_kdc_credentials_form_test.js
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+var credentialUtils = require('utils/credentials');
+
+var view;
+
+describe('#App.ManageCredentialsFormView', function() {
+  beforeEach(function() {
+    view = App.ManageCredentialsFormView.create({
+      parentView: Em.Object.create({})
+    });
+  });
+
+  afterEach(function() {
+    view.destroy();
+  });
+
+  describe('#prepareContent', function() {
+    [
+      {
+        isStorePersistent: true,
+        credentials: [
+          {
+            alias: 'kdc.admin.credential',
+            type: 'persisted'
+          }
+        ],
+        e: {
+          isRemovable: true,
+          isRemoveDisabled: false,
+          storePersisted: true
+        },
+        m: 'persistent store is available, previous credentials were stored as persisted. Remove button should be visible and active.'
+      },
+      {
+        isStorePersistent: true,
+        credentials: [
+          {
+            alias: 'kdc.admin.credential',
+            type: 'temporary'
+          }
+        ],
+        e: {
+          isRemovable: false,
+          isRemoveDisabled: true,
+          storePersisted: true
+        },
+        m: 'persistent store is available, previous credentials were stored as temporary. Remove button should be hidden and disabled.'
+      }
+    ].forEach(function(test) {
+      it(test.m, function(done) {
+        sinon.stub(credentialUtils, 'credentials', function(clusterName, callback) {
+          callback(test.credentials);
+        });
+        sinon.stub(credentialUtils, 'isStorePersisted', function() {
+          return $.Deferred().resolve(test.isStorePersistent).promise();
+        });
+        view.prepareContent();
+        Em.run.next(function() {
+          assert.equal(view.get('isRemovable'), test.e.isRemovable, '#isRemovable property validation');
+          assert.equal(view.get('isRemoveDisabled'), test.e.isRemoveDisabled, '#isRemoveDisabled property validation');
+          assert.equal(view.get('storePersisted'), test.e.storePersisted, '#storePersisted property validation');
+          credentialUtils.credentials.restore();
+          credentialUtils.isStorePersisted.restore();
+          done();
+        });
+      });
+    });
+  });
+
+  describe('#isSubmitDisabled', function() {
+    it('save button disabled by default', function() {
+      expect(view.get('isSubmitDisabled')).to.be.true;
+    });
+    it('save button disabled when password is empty', function() {
+      view.set('principal', 'some_principal');
+      expect(view.get('isSubmitDisabled')).to.be.true;
+    });
+    it('save button disabled when principal is empty', function() {
+      view.set('password', 'some_password');
+      expect(view.get('isSubmitDisabled')).to.be.true;
+    });
+    it('save button should be enabled when principal and password are filled', function() {
+      view.set('password', 'some_password');
+      view.set('principal', 'principal');
+      expect(view.get('isSubmitDisabled')).to.be.false;
+    });
+  });
+
+  describe('#removeKDCCredentials', function() {
+    it('should show confirmation popup', function() {
+      var popup = view.removeKDCCredentials();
+      expect(popup).be.instanceof(App.ModalPopup);
+      popup.destroy();
+    });
+    it('should call credentialUtils#removeCredentials', function() {
+      this.clock = sinon.useFakeTimers();
+      var popup = view.removeKDCCredentials();
+      assert.isFalse(view.get('actionStatus'), '#actionStatus before remove');
+      sinon.stub(credentialUtils, 'removeCredentials', function() {
+        var dfd = $.Deferred();
+        setTimeout(function() {
+          dfd.resolve();
+        }, 500);
+        return dfd.promise();
+      });
+      popup.onPrimary();
+      assert.isTrue(view.get('isActionInProgress'), 'action in progress');
+      assert.isTrue(view.get('isRemoveDisabled'), 'remove button disabled');
+      assert.isTrue(view.get('isSubmitDisabled'), 'submit button disabled');
+      this.clock.tick(1000);
+      assert.isFalse(view.get('isActionInProgress'), 'action finished');
+      assert.equal(Em.I18n.t('common.success'), view.get('actionStatus'), '#actionStatus after remove');
+      assert.isTrue(view.get('parentView.isCredentialsRemoved'), 'parentView#isCredentialsRemoved property should be triggered when remove complete');
+      credentialUtils.removeCredentials.restore();
+      this.clock.restore();
+      popup.destroy();
+    });
+  });
+
+});


[2/2] ambari git commit: AMBARI-13448. CSV export: downloaded data does not reflect what's shown in the chart

Posted by al...@apache.org.
AMBARI-13448. CSV export: downloaded data does not reflect what's shown in the chart


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

Branch: refs/heads/trunk
Commit: 4a954283e96a1e598e436b9c264939398793c59d
Parents: a509510
Author: Alex Antonenko <hi...@gmail.com>
Authored: Fri Oct 16 13:08:00 2015 +0300
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Fri Oct 16 13:38:21 2015 +0300

----------------------------------------------------------------------
 .../views/common/widget/graph_widget_view.js    |   2 +-
 .../common/widget/graph_widget_view_test.js     | 112 +++++++++++++++++++
 2 files changed, 113 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/4a954283/ambari-web/app/views/common/widget/graph_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/widget/graph_widget_view.js b/ambari-web/app/views/common/widget/graph_widget_view.js
index fb8e754..1ed20d2 100644
--- a/ambari-web/app/views/common/widget/graph_widget_view.js
+++ b/ambari-web/app/views/common/widget/graph_widget_view.js
@@ -313,7 +313,7 @@ App.GraphWidgetView = Em.View.extend(App.WidgetMixin, App.ExportMetricsMixin, {
       isCSV = !!event.context,
       fileType = isCSV ? 'csv' : 'json',
       fileName = 'data.' + fileType,
-      metrics = this.get('content.metrics'),
+      metrics = this.get('data'),
       hasData = Em.isArray(metrics) && metrics.some(function (item) {
         return Em.isArray(item.data);
       });

http://git-wip-us.apache.org/repos/asf/ambari/blob/4a954283/ambari-web/test/views/common/widget/graph_widget_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/widget/graph_widget_view_test.js b/ambari-web/test/views/common/widget/graph_widget_view_test.js
index deccce5..7215128 100644
--- a/ambari-web/test/views/common/widget/graph_widget_view_test.js
+++ b/ambari-web/test/views/common/widget/graph_widget_view_test.js
@@ -18,6 +18,7 @@
 
 var App = require('app');
 require('views/common/widget/graph_widget_view');
+var fileUtils = require('utils/file_utils');
 
 describe('App.GraphWidgetView', function () {
   var view = App.GraphWidgetView.create();
@@ -126,4 +127,115 @@ describe('App.GraphWidgetView', function () {
     });
   });
 
+  describe('#exportGraphData', function () {
+
+    var cases = [
+      {
+        data: null,
+        prepareCSVCallCount: 0,
+        prepareJSONCallCount: 0,
+        downloadTextFileCallCount: 0,
+        showAlertPopupCallCount: 1,
+        title: 'no data'
+      },
+      {
+        data: {},
+        prepareCSVCallCount: 0,
+        prepareJSONCallCount: 0,
+        downloadTextFileCallCount: 0,
+        showAlertPopupCallCount: 1,
+        title: 'invalid data'
+      },
+      {
+        data: [
+          {
+            data: null
+          }
+        ],
+        prepareCSVCallCount: 0,
+        prepareJSONCallCount: 0,
+        downloadTextFileCallCount: 0,
+        showAlertPopupCallCount: 1,
+        title: 'empty data'
+      },
+      {
+        data: [
+          {
+            data: {}
+          }
+        ],
+        prepareCSVCallCount: 0,
+        prepareJSONCallCount: 0,
+        downloadTextFileCallCount: 0,
+        showAlertPopupCallCount: 1,
+        title: 'malformed data'
+      },
+      {
+        data: [
+          {
+            data: [
+              {
+                key: 'value'
+              }
+            ]
+          }
+        ],
+        prepareCSVCallCount: 0,
+        prepareJSONCallCount: 1,
+        downloadTextFileCallCount: 1,
+        showAlertPopupCallCount: 0,
+        title: 'JSON export'
+      },
+      {
+        data: [
+          {
+            data: [
+              {
+                key: 'value'
+              }
+            ]
+          }
+        ],
+        event: {
+          context: true
+        },
+        prepareCSVCallCount: 1,
+        prepareJSONCallCount: 0,
+        downloadTextFileCallCount: 1,
+        showAlertPopupCallCount: 0,
+        title: 'CSV export'
+      }
+    ];
+
+    beforeEach(function () {
+      sinon.stub(view, 'prepareCSV').returns([]);
+      sinon.stub(view, 'prepareJSON').returns([]);
+      sinon.stub(fileUtils, 'downloadTextFile', Em.K);
+      sinon.stub(App, 'showAlertPopup', Em.K);
+    });
+
+    afterEach(function () {
+      view.prepareCSV.restore();
+      view.prepareJSON.restore();
+      fileUtils.downloadTextFile.restore();
+      App.showAlertPopup.restore();
+    });
+
+    cases.forEach(function (item) {
+      it(item.title, function () {
+        view.set('data', item.data);
+        view.exportGraphData(item.event || {});
+        expect(view.prepareCSV.callCount).to.equal(item.prepareCSVCallCount);
+        expect(view.prepareJSON.callCount).to.equal(item.prepareJSONCallCount);
+        expect(fileUtils.downloadTextFile.callCount).to.equal(item.downloadTextFileCallCount);
+        expect(App.showAlertPopup.callCount).to.equal(item.showAlertPopupCallCount);
+        if (item.downloadTextFileCallCount) {
+          var fileType = item.event && item.event.context ? 'csv' : 'json';
+          expect(fileUtils.downloadTextFile.calledWith([], fileType, 'data.' + fileType)).to.be.true;
+        }
+      });
+    });
+
+  });
+
 });
\ No newline at end of file