AMBARI-9264 Stack and Versions: Integrate view/edit repositories with API. (ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/11efa2f5 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/11efa2f5 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/11efa2f5 Branch: refs/heads/trunk Commit: 11efa2f5580b28acb3484586c2f74d5b9adfc17d Parents: 5f4adaa Author: aBabiichuk <ababiic...@cybervisiontech.com> Authored: Thu Jan 22 16:41:20 2015 +0200 Committer: aBabiichuk <ababiic...@cybervisiontech.com> Committed: Thu Jan 22 16:41:20 2015 +0200 ---------------------------------------------------------------------- ambari-web/app/assets/test/tests.js | 2 +- .../main/admin/stack_and_upgrade_controller.js | 111 +- ambari-web/app/messages.js | 1 + .../admin/stack_upgrade/edit_repositories.hbs | 20 +- ambari-web/app/utils/ajax/ajax.js | 26 + ambari-web/app/views.js | 2 +- ambari-web/app/views/common/controls_view.js | 1224 ++++++++++++++++++ .../stack_upgrade/upgrade_version_box_view.js | 40 +- ambari-web/app/views/wizard/controls_view.js | 1208 ----------------- .../admin/stack_and_upgrade_controller_test.js | 50 + .../test/views/common/controls_view_test.js | 606 +++++++++ .../test/views/wizard/controls_view_test.js | 606 --------- 12 files changed, 2064 insertions(+), 1832 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/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 33abd6e..d02a375 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -211,10 +211,10 @@ var files = ['test/init_model_test', 'test/views/common/configs/service_config_container_view_test', 'test/views/common/configs/service_configs_by_category_view_test', 'test/views/common/configs/custom_category_views/notification_configs_view_test', + 'test/views/common/controls_view_test', 'test/views/wizard/step3/hostLogPopupBody_view_test', 'test/views/wizard/step3/hostWarningPopupBody_view_test', 'test/views/wizard/step3/hostWarningPopupFooter_view_test', - 'test/views/wizard/controls_view_test', 'test/views/wizard/step0_view_test', 'test/views/wizard/step1_view_test', 'test/views/wizard/step2_view_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/ambari-web/app/controllers/main/admin/stack_and_upgrade_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/stack_and_upgrade_controller.js b/ambari-web/app/controllers/main/admin/stack_and_upgrade_controller.js index 232826d..d98a816 100644 --- a/ambari-web/app/controllers/main/admin/stack_and_upgrade_controller.js +++ b/ambari-web/app/controllers/main/admin/stack_and_upgrade_controller.js @@ -401,8 +401,115 @@ App.MainAdminStackAndUpgradeController = Em.Controller.extend(App.LocalStorage, }); }, - saveRepoOS: function () { - //TODO integrate with API + /** + * transform repo data into json for + * saving changes to repository version + * @param {Em.Object} repo + * @returns {{operating_systems: Array}} + */ + prepareRepoForSaving: function(repo) { + var repoVersion = { "operating_systems": [] }; + + repo.get('operatingSystems').forEach(function (os, k) { + repoVersion.operating_systems.push({ + "OperatingSystems": { + "os_type": os.get("osType") + }, + "repositories": [] + }); + os.get('repositories').forEach(function (repository) { + repoVersion.operating_systems[k].repositories.push({ + "Repositories": { + "base_url": repository.get('baseUrl'), + "repo_id": repository.get('repoId'), + "repo_name": repository.get('repoName') + } + }); + }); + }); + return repoVersion; + }, + + /** + * perform validation if <code>skip<code> is false and run save if + * validation successfull or run save without validation is <code>skip<code> is true + * @param {Em.Object} repo + * @param {boolean} skip + * @returns {$.Deferred} + */ + saveRepoOS: function (repo, skip) { + var self = this; + var deferred = $.Deferred(); + this.validateRepoVersions(repo, skip).done(function(data) { + if (data.length > 0) { + deferred.resolve(data); + } else { + var repoVersion = self.prepareRepoForSaving(repo); + + App.ajax.send({ + name: 'admin.stack_versions.edit.repo', + sender: this, + data: { + stackName: App.get('currentStackName'), + stackVersion: App.get('currentStackVersionNumber'), + repoVersionId: repo.get('repoVersionId'), + repoVersion: repoVersion + } + }).success(function() { + deferred.resolve([]); + }); + } + }); + return deferred.promise(); + }, + + /** + * send request for validation for each repository + * @param {Em.Object} repo + * @param {boolean} skip + * @returns {*} + */ + validateRepoVersions: function(repo, skip) { + var deferred = $.Deferred(), + totalCalls = 0, + invalidUrls = []; + + if (skip) { + deferred.resolve(invalidUrls); + } else { + repo.get('operatingSystems').forEach(function (os) { + if (os.get('isSelected')) { + os.get('repositories').forEach(function (repo) { + totalCalls++; + App.ajax.send({ + name: 'admin.stack_versions.validate.repo', + sender: this, + data: { + repo: repo, + repoId: repo.get('repoId'), + baseUrl: repo.get('baseUrl'), + osType: os.get('osType'), + stackName: App.get('currentStackName'), + stackVersion: App.get('currentStackVersionNumber') + } + }) + .success(function () { + totalCalls--; + if (totalCalls === 0) deferred.resolve(invalidUrls); + }) + .error(function () { + repo.set('hasError', true); + invalidUrls.push(repo); + totalCalls--; + if (totalCalls === 0) deferred.resolve(invalidUrls); + }); + }); + } else { + return deferred.resolve(invalidUrls); + } + }); + } + return deferred.promise(); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index d8026a6..548bb4c 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -1311,6 +1311,7 @@ Em.I18n.translations = { 'admin.stackVersions.filter.current': "Current ({0})", 'admin.stackVersions.editRepositories.info': 'Provide Base URLs for the Operating Systems you are configuring. Uncheck all other Operating Systems.', + 'admin.stackVersions.editRepositories.validation.warning': 'Some of the repositories failed validation. Make changes to the base url or skip validation if you are sure that urls are correct', 'admin.stackVersions.version.install.confirm': 'You are about to install version <strong>{0}</strong> on all hosts.', 'admin.stackVersions.version.linkTooltip': 'Click to Edit Repositories', 'admin.stackVersions.version.hostsTooltip': 'Click to List Hosts', http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/ambari-web/app/templates/main/admin/stack_upgrade/edit_repositories.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/admin/stack_upgrade/edit_repositories.hbs b/ambari-web/app/templates/main/admin/stack_upgrade/edit_repositories.hbs index e3fbd9d..dc65f01 100644 --- a/ambari-web/app/templates/main/admin/stack_upgrade/edit_repositories.hbs +++ b/ambari-web/app/templates/main/admin/stack_upgrade/edit_repositories.hbs @@ -19,7 +19,9 @@ <div class="alert alert-info"> {{t admin.stackVersions.editRepositories.info}} </div> - +<div {{bindAttr class="view.parentView.hasErrors::hidden :alert :alert-warning"}}> + {{t admin.stackVersions.editRepositories.validation.warning}} +</div> <div class="row-fluid"> <div class="span2"><strong>{{t common.os}}</strong></div> <div class="span10 row-fluid"> @@ -30,13 +32,13 @@ {{#each os in view.content.operatingSystems}} <div class="row-fluid os-block"> <div class="span2"> - {{view Ember.Checkbox class="align-checkbox" checkedBinding="os.isSelected"}} {{os.osType}} + {{view view.OSCheckBox osBinding="os"}} {{os.osType}} </div> - <div class="span10"> - {{#each repository in os.repositories}} - <div class="row-fluid"> - <div class="span3">{{repository.repoName}}</div> - <div class="span9">{{view Ember.TextField valueBinding="repository.baseUrl" disabledBinding="os.isDisabled"}}</div> + <div class="span10"> + {{#each repository in os.repositories}} + <div class="row-fluid"> + <div class="span3">{{repository.repoName}}</div> + <div {{bindAttr class="repository.hasError:error :control-group :span9"}}>{{view App.BaseUrlTextField repositoryBinding="repository" disabledBinding="os.isDisabled"}}</div> </div> {{/each}} </div> @@ -44,7 +46,7 @@ {{/each}} <div> - <label>{{view Ember.Checkbox checkedBinding="view.skipValidation" class="align-checkbox"}}{{t installer.step1.advancedRepo.skipValidation.message}} + <label>{{view view.skipCheckBox checkedBinding="view.parentView.skipValidation"}}{{t installer.step1.advancedRepo.skipValidation.message}} <i class="icon-question-sign" rel="skip-validation-tooltip" - data-toggle="tooltip" {{translateAttr title="installer.step1.advancedRepo.skipValidation.tooltip"}}></i></label> + data-toggle="tooltip" {{translateAttr title="installer.step1.advancedRepo.skipValidation.tooltip"}}></i></label> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/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 2e199b5..1f343dc 100644 --- a/ambari-web/app/utils/ajax/ajax.js +++ b/ambari-web/app/utils/ajax/ajax.js @@ -1393,6 +1393,32 @@ var urls = { }, 'mock': '' }, + + 'admin.stack_versions.edit.repo': { + 'real': '/stacks/{stackName}/versions/{stackVersion}/repository_versions/{repoVersionId}', + 'mock': '', + 'type': 'PUT', + 'format': function (data) { + return { + data: JSON.stringify(data.repoVersion) + } + } + }, + 'admin.stack_versions.validate.repo': { + 'real': '/stacks/{stackName}/versions/{stackVersion}/operating_systems/{osType}/repositories/{repoId}?validate_only=true', + 'mock': '', + 'type': 'POST', + 'format': function (data) { + return { + data: JSON.stringify({ + "Repositories": { + "base_url": data.baseUrl + } + }) + } + } + }, + 'admin.rolling_upgrade.pre_upgrade_check': { 'real': '/clusters/{clusterName}/rolling_upgrades_check?fields=*&UpgradeChecks/repository_version={value}', 'mock': '/data/stack_versions/pre_upgrade_check.json' http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/ambari-web/app/views.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js index eb3a284..f981568 100644 --- a/ambari-web/app/views.js +++ b/ambari-web/app/views.js @@ -53,6 +53,7 @@ require('views/common/filter_combobox'); require('views/common/filter_combo_cleanable'); require('views/common/table_view'); require('views/common/progress_bar_view'); +require('views/common/controls_view'); require('views/login'); require('views/main'); require('views/main/menu'); @@ -278,7 +279,6 @@ require('views/main/charts/heatmap/heatmap_host_detail'); require('views/main/views_view'); require('views/installer'); -require('views/wizard/controls_view'); require('views/wizard/step0_view'); require('views/wizard/step1_view'); require('views/wizard/step2_view'); http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/ambari-web/app/views/common/controls_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/controls_view.js b/ambari-web/app/views/common/controls_view.js new file mode 100644 index 0000000..7a85ad4 --- /dev/null +++ b/ambari-web/app/views/common/controls_view.js @@ -0,0 +1,1224 @@ +/** + * 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'); + +/** + * Abstract view for config fields. + * Add popover support to control + */ +App.ServiceConfigPopoverSupport = Ember.Mixin.create({ + + /** + * Config object. It will instance of App.ServiceConfigProperty + */ + serviceConfig: null, + attributeBindings:['readOnly'], + isPopoverEnabled: true, + + didInsertElement: function () { + $('body').tooltip({ + selector: '[data-toggle=tooltip]', + placement: 'top' + }); + // if description for this serviceConfig not exist, then no need to show popover + if (this.get('isPopoverEnabled') !== 'false' && this.get('serviceConfig.description')) { + App.popover(this.$(), { + title: Em.I18n.t('installer.controls.serviceConfigPopover.title').format( + this.get('serviceConfig.displayName'), + (this.get('serviceConfig.displayName') == this.get('serviceConfig.name')) ? '' : this.get('serviceConfig.name') + ), + content: this.get('serviceConfig.description'), + placement: 'right', + trigger: 'hover' + }); + } + }, + + willDestroyElement: function() { + this.$().popover('destroy'); + }, + + readOnly: function () { + return !this.get('serviceConfig.isEditable'); + }.property('serviceConfig.isEditable') +}); + +/** + * mixin set class that serve as unique element identificator, + * id not used in order to avoid collision with ember ids + */ +App.ServiceConfigCalculateId = Ember.Mixin.create({ + idClass: Ember.computed(function () { + var label = Em.get(this, 'serviceConfig.name') ? Em.get(this, 'serviceConfig.name').toLowerCase().replace(/\./g, '-') : '', + fileName = Em.get(this, 'serviceConfig.filename') ? Em.get(this, 'serviceConfig.filename').toLowerCase().replace(/\./g, '-') : '', + group = Em.get(this, 'serviceConfig.group.name') || 'default'; + isOrigin = Em.get(this, 'serviceConfig.compareConfigs.length') > 0 ? '-origin' : ''; + return 'service-config-' + label + '-' + fileName + '-' + group + isOrigin; + }), + classNameBindings: 'idClass' +}); + +/** + * Default input control + * @type {*} + */ +App.ServiceConfigTextField = Ember.TextField.extend(App.ServiceConfigPopoverSupport, App.ServiceConfigCalculateId, { + + valueBinding: 'serviceConfig.value', + classNameBindings: 'textFieldClassName', + placeholderBinding: 'serviceConfig.defaultValue', + + keyPress: function (event) { + if (event.keyCode == 13) { + return false; + } + }, + //Set editDone true for last edited config text field parameter + focusOut: function (event) { + this.get('serviceConfig').set("editDone", true); + }, + //Set editDone false for all current category config text field parameter + focusIn: function (event) { + if (!this.get('serviceConfig.isOverridden') && !this.get('serviceConfig.isComparison')) { + this.get("parentView.categoryConfigsAll").setEach("editDone", false); + } + }, + + textFieldClassName: function () { + if (this.get('serviceConfig.unit')) { + return ['input-small']; + } else if (this.get('serviceConfig.displayType') === 'principal') { + return ['span12']; + } else { + return ['span9']; + } + }.property('serviceConfig.displayType', 'serviceConfig.unit') + +}); + +/** + * Customized input control with Units type specified + * @type {Em.View} + */ +App.ServiceConfigTextFieldWithUnit = Ember.View.extend(App.ServiceConfigPopoverSupport, { + valueBinding: 'serviceConfig.value', + classNames: ['input-append', 'with-unit'], + placeholderBinding: 'serviceConfig.defaultValue', + + templateName: require('templates/wizard/controls_service_config_textfield_with_unit') +}); + +/** + * Password control + * @type {*} + */ +App.ServiceConfigPasswordField = Ember.TextField.extend({ + + serviceConfig: null, + type: 'password', + attributeBindings:['readOnly'], + valueBinding: 'serviceConfig.value', + classNames: [ 'span4' ], + placeholder: Em.I18n.t('form.item.placeholders.typePassword'), + + template: Ember.Handlebars.compile('{{view view.retypePasswordView}}'), + + keyPress: function (event) { + if (event.keyCode == 13) { + return false; + } + }, + + retypePasswordView: Ember.TextField.extend({ + placeholder: Em.I18n.t('form.passwordRetype'), + attributeBindings:['readOnly'], + type: 'password', + classNames: [ 'span4', 'retyped-password' ], + keyPress: function (event) { + if (event.keyCode == 13) { + return false; + } + }, + valueBinding: 'parentView.serviceConfig.retypedPassword', + readOnly: function () { + return !this.get('parentView.serviceConfig.isEditable'); + }.property('parentView.serviceConfig.isEditable') + }), + + readOnly: function () { + return !this.get('serviceConfig.isEditable'); + }.property('serviceConfig.isEditable') + +}); + +/** + * Textarea control + * @type {*} + */ +App.ServiceConfigTextArea = Ember.TextArea.extend(App.ServiceConfigPopoverSupport, App.ServiceConfigCalculateId, { + + valueBinding: 'serviceConfig.value', + rows: 4, + classNames: ['span9', 'directories'] +}); + +/** + * Textarea control for content type + * @type {*} + */ +App.ServiceConfigTextAreaContent = Ember.TextArea.extend(App.ServiceConfigPopoverSupport, App.ServiceConfigCalculateId, { + + valueBinding: 'serviceConfig.value', + rows: 20, + classNames: ['span10'] +}); + +/** + * Textarea control with bigger height + * @type {*} + */ +App.ServiceConfigBigTextArea = App.ServiceConfigTextArea.extend(App.ServiceConfigCalculateId, { + rows: 10 +}); + +/** + * Checkbox control + * @type {*} + */ +App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSupport, App.ServiceConfigCalculateId, { + + checkedBinding: 'serviceConfig.value', + + disabled: function () { + return !this.get('serviceConfig.isEditable'); + }.property('serviceConfig.isEditable') +}); + +App.ServiceConfigRadioButtons = Ember.View.extend(App.ServiceConfigCalculateId, { + templateName: require('templates/wizard/controls_service_config_radio_buttons'), + + didInsertElement: function () { + // on page render, automatically populate JDBC URLs only for default database settings + // so as to not lose the user's customizations on these fields + if (['addServiceController', 'installerController'].contains(this.get('controller.wizardController.name'))) { + if (/^New\s\w+\sDatabase$/.test(this.get('serviceConfig.value'))) { + this.onOptionsChange(); + } else { + this.handleDBConnectionProperty(); + } + } + }, + + configs: function () { + if (this.get('controller.name') == 'mainServiceInfoConfigsController') return this.get('categoryConfigsAll'); + return this.get('categoryConfigsAll').filterProperty('isObserved', true); + }.property('categoryConfigsAll'), + + serviceConfig: null, + categoryConfigsAll: null, + + onOptionsChange: function () { + // The following if condition will be satisfied only for installer wizard flow + if (this.get('configs').length) { + var connectionUrl = this.get('connectionUrl'); + var dbClass = this.get('dbClass'); + if (connectionUrl) { + if (this.get('serviceConfig.serviceName') === 'HIVE') { + var hiveDbType = this.get('parentView.serviceConfigs').findProperty('name', 'hive_database_type'); + switch (this.get('serviceConfig.value')) { + case 'New MySQL Database': + case 'Existing MySQL Database': + connectionUrl.set('value', "jdbc:mysql://" + this.get('hostName') + "/" + this.get('databaseName') + "?createDatabaseIfNotExist=true"); + dbClass.set('value', "com.mysql.jdbc.Driver"); + Em.set(hiveDbType, 'value', 'mysql'); + break; + case Em.I18n.t('services.service.config.hive.oozie.postgresql'): + connectionUrl.set('value', "jdbc:postgresql://" + this.get('hostName') + ":5432/" + this.get('databaseName')); + dbClass.set('value', "org.postgresql.Driver"); + Em.set(hiveDbType, 'value', 'postgres'); + break; + case 'Existing Oracle Database': + connectionUrl.set('value', "jdbc:oracle:thin:@//" + this.get('hostName') + ":1521/" + this.get('databaseName')); + dbClass.set('value', "oracle.jdbc.driver.OracleDriver"); + Em.set(hiveDbType, 'value', 'oracle'); + break; + case 'Existing MSSQL Server database with SQL authentication': + connectionUrl.set('value', "jdbc:sqlserver://" + this.get('hostName') + ";databaseName=" + this.get('databaseName')); + dbClass.set('value', "com.microsoft.sqlserver.jdbc.SQLServerDriver"); + Em.set(hiveDbType, 'value', 'mssql'); + break; + case 'Existing MSSQL Server database with integrated authentication': + connectionUrl.set('value', "jdbc:sqlserver://" + this.get('hostName') + ";databaseName=" + this.get('databaseName') + ";integratedSecurity=true"); + dbClass.set('value', "com.microsoft.sqlserver.jdbc.SQLServerDriver"); + Em.set(hiveDbType, 'value', 'mssql'); + break; + } + var isNotExistingMySQLServer = this.get('serviceConfig.value') !== 'Existing MSSQL Server database with integrated authentication'; + this.get('categoryConfigsAll').findProperty('name', 'javax.jdo.option.ConnectionUserName').setProperties({ + isVisible: isNotExistingMySQLServer, + isRequired: isNotExistingMySQLServer + }); + this.get('categoryConfigsAll').findProperty('name', 'javax.jdo.option.ConnectionPassword').setProperties({ + isVisible: isNotExistingMySQLServer, + isRequired: isNotExistingMySQLServer + }); + } else if (this.get('serviceConfig.serviceName') === 'OOZIE') { + switch (this.get('serviceConfig.value')) { + case 'New Derby Database': + connectionUrl.set('value', "jdbc:derby:${oozie.data.dir}/${oozie.db.schema.name}-db;create=true"); + dbClass.set('value', "org.apache.derby.jdbc.EmbeddedDriver"); + break; + case 'Existing MySQL Database': + connectionUrl.set('value', "jdbc:mysql://" + this.get('hostName') + "/" + this.get('databaseName')); + dbClass.set('value', "com.mysql.jdbc.Driver"); + break; + case Em.I18n.t('services.service.config.hive.oozie.postgresql'): + connectionUrl.set('value', "jdbc:postgresql://" + this.get('hostName') + ":5432/" + this.get('databaseName')); + dbClass.set('value', "org.postgresql.Driver"); + break; + case 'Existing Oracle Database': + connectionUrl.set('value', "jdbc:oracle:thin:@//" + this.get('hostName') + ":1521/" + this.get('databaseName')); + dbClass.set('value', "oracle.jdbc.driver.OracleDriver"); + break; + case 'Existing MSSQL Server database with SQL authentication': + connectionUrl.set('value', "jdbc:sqlserver://" + this.get('hostName') + ";databaseName=" + this.get('databaseName')); + dbClass.set('value', "com.microsoft.sqlserver.jdbc.SQLServerDriver"); + break; + case 'Existing MSSQL Server database with integrated authentication': + connectionUrl.set('value', "jdbc:sqlserver://" + this.get('hostName') + ";databaseName=" + this.get('databaseName') + ";integratedSecurity=true"); + dbClass.set('value', "com.microsoft.sqlserver.jdbc.SQLServerDriver"); + break; + } + isNotExistingMySQLServer = this.get('serviceConfig.value') !== 'Existing MSSQL Server database with integrated authentication'; + this.get('categoryConfigsAll').findProperty('name', 'oozie.service.JPAService.jdbc.username').setProperties({ + isVisible: isNotExistingMySQLServer, + isRequired: isNotExistingMySQLServer + }); + this.get('categoryConfigsAll').findProperty('name', 'oozie.service.JPAService.jdbc.password').setProperties({ + isVisible: isNotExistingMySQLServer, + isRequired: isNotExistingMySQLServer + }); + } + connectionUrl.set('defaultValue', connectionUrl.get('value')); + } + } + }.observes('databaseName', 'hostName'), + + nameBinding: 'serviceConfig.radioName', + + databaseNameProperty: function () { + switch (this.get('serviceConfig.serviceName')) { + case 'HIVE': + return this.get('categoryConfigsAll').findProperty('name', 'ambari.hive.db.schema.name'); + case 'OOZIE': + return this.get('categoryConfigsAll').findProperty('name', 'oozie.db.schema.name'); + default: + return null; + } + }.property('serviceConfig.serviceName'), + + databaseName: function () { + return this.get('databaseNameProperty.value'); + }.property('databaseNameProperty.value'), + + hostNameProperty: function () { + var value = this.get('serviceConfig.value'); + var returnValue; + var hostname; + if (this.get('serviceConfig.serviceName') === 'HIVE') { + switch (value) { + case 'New MySQL Database': + hostname = this.get('categoryConfigsAll').findProperty('name', 'hive_ambari_host'); + break; + case 'Existing MySQL Database': + hostname = this.get('categoryConfigsAll').findProperty('name', 'hive_existing_mysql_host'); + break; + case Em.I18n.t('services.service.config.hive.oozie.postgresql'): + hostname = this.get('categoryConfigsAll').findProperty('name', 'hive_existing_postgresql_host'); + break; + case 'Existing Oracle Database': + hostname = this.get('categoryConfigsAll').findProperty('name', 'hive_existing_oracle_host'); + break; + case 'Existing MSSQL Server database with SQL authentication': + hostname = this.get('categoryConfigsAll').findProperty('name', 'hive_existing_mssql_server_host'); + break; + case 'Existing MSSQL Server database with integrated authentication': + hostname = this.get('categoryConfigsAll').findProperty('name', 'hive_existing_mssql_server_2_host'); + break; + } + if (hostname) { + returnValue = hostname; + } else { + returnValue = this.get('categoryConfigsAll').findProperty('name', 'hive_hostname'); + } + } else if (this.get('serviceConfig.serviceName') === 'OOZIE') { + switch (value) { + case 'New Derby Database': + hostname = this.get('categoryConfigsAll').findProperty('name', 'oozie_ambari_host'); + break; + case 'Existing MySQL Database': + hostname = this.get('categoryConfigsAll').findProperty('name', 'oozie_existing_mysql_host'); + break; + case Em.I18n.t('services.service.config.hive.oozie.postgresql'): + hostname = this.get('categoryConfigsAll').findProperty('name', 'oozie_existing_postgresql_host'); + break; + case 'Existing Oracle Database': + hostname = this.get('categoryConfigsAll').findProperty('name', 'oozie_existing_oracle_host'); + break; + case 'Existing MSSQL Server database with SQL authentication': + hostname = this.get('categoryConfigsAll').findProperty('name', 'oozie_existing_mssql_server_host'); + break; + case 'Existing MSSQL Server database with integrated authentication': + hostname = this.get('categoryConfigsAll').findProperty('name', 'oozie_existing_mssql_server_2_host'); + break; + } + if (hostname) { + returnValue = hostname; + } else { + returnValue = this.get('categoryConfigsAll').findProperty('name', 'oozie_hostname'); + } + } + return returnValue; + }.property('serviceConfig.serviceName', 'serviceConfig.value'), + + hostName: function () { + return this.get('hostNameProperty.value'); + }.property('hostNameProperty.value'), + + connectionUrl: function () { + if (this.get('serviceConfig.serviceName') === 'HIVE') { + return this.get('categoryConfigsAll').findProperty('name', 'javax.jdo.option.ConnectionURL'); + } else { + return this.get('categoryConfigsAll').findProperty('name', 'oozie.service.JPAService.jdbc.url'); + } + }.property('serviceConfig.serviceName'), + + dbClass: function () { + if (this.get('serviceConfig.serviceName') === 'HIVE') { + return this.get('categoryConfigsAll').findProperty('name', 'javax.jdo.option.ConnectionDriverName'); + } else { + return this.get('categoryConfigsAll').findProperty('name', 'oozie.service.JPAService.jdbc.driver'); + } + }.property('serviceConfig.serviceName'), + + /** + * `Observer` that add <code>additionalView</code> to <code>App.ServiceConfigProperty</code> + * that responsible for (if existing db selected) + * 1. checking database connection + * 2. showing jdbc driver setup warning msg. + * + * @method handleDBConnectionProperty + **/ + handleDBConnectionProperty: function() { + var handledProperties = ['oozie_database', 'hive_database']; + var currentValue = this.get('serviceConfig.value'); + var databases = /MySQL|PostgreSQL|Oracle|Derby|MSSQL/gi; + var currentDB = currentValue.match(databases)[0]; + var databasesTypes = /MySQL|PostgreS|Oracle|Derby|MSSQL/gi; + var currentDBType = currentValue.match(databasesTypes)[0]; + var existingDatabase = /existing/gi.test(currentValue); + // db connection check button show up if existed db selected + var propertyAppendTo1 = this.get('categoryConfigsAll').findProperty('displayName', 'Database URL'); + if (currentDB && existingDatabase) { + if (handledProperties.contains(this.get('serviceConfig.name'))) { + if (propertyAppendTo1) propertyAppendTo1.set('additionalView', App.CheckDBConnectionView.extend({databaseName: currentDB})); + } + } else { + propertyAppendTo1.set('additionalView', null); + } + // warning msg under database type radio buttons, to warn the user to setup jdbc driver if existed db selected + var propertyHive = this.get('categoryConfigsAll').findProperty('displayName', 'Hive Database'); + var propertyOozie = this.get('categoryConfigsAll').findProperty('displayName', 'Oozie Database'); + var propertyAppendTo2 = propertyHive ? propertyHive : propertyOozie; + if (currentDB && existingDatabase) { + if (handledProperties.contains(this.get('serviceConfig.name'))) { + if (propertyAppendTo2) { + propertyAppendTo2.set('additionalView', Ember.View.extend({ + template: Ember.Handlebars.compile('<div class="alert">{{{view.message}}}</div>'), + message: Em.I18n.t('services.service.config.database.msg.jdbcSetup').format(currentDBType.toLowerCase(), currentDBType.toLowerCase()) + })); + } + } + } else { + propertyAppendTo2.set('additionalView', null); + } + }.observes('serviceConfig.value'), + + optionsBinding: 'serviceConfig.options' +}); + +App.ServiceConfigRadioButton = Ember.Checkbox.extend({ + tagName: 'input', + attributeBindings: ['type', 'name', 'value', 'checked', 'disabled'], + checked: false, + type: 'radio', + name: null, + value: null, + + didInsertElement: function () { + console.debug('App.ServiceConfigRadioButton.didInsertElement'); + if (this.get('parentView.serviceConfig.value') === this.get('value')) { + console.debug(this.get('name') + ":" + this.get('value') + ' is checked'); + this.set('checked', true); + } + }, + + click: function () { + this.set('checked', true); + console.debug('App.ServiceConfigRadioButton.click'); + this.onChecked(); + }, + + onChecked: function () { + // Wrapping the call with Ember.run.next prevents a problem where setting isVisible on component + // causes JS error due to re-rendering. For example, this occurs when switching the Config Group + // in Service Config page + Em.run.next(this, function() { + console.debug('App.ServiceConfigRadioButton.onChecked'); + this.set('parentView.serviceConfig.value', this.get('value')); + var components = this.get('parentView.serviceConfig.options'); + if (components) { + components.forEach(function (_component) { + if (_component.foreignKeys) { + _component.foreignKeys.forEach(function (_componentName) { + if (this.get('parentView.categoryConfigsAll').someProperty('name', _componentName)) { + var component = this.get('parentView.categoryConfigsAll').findProperty('name', _componentName); + component.set('isVisible', _component.displayName === this.get('value')); + } + }, this); + } + }, this); + } + }); + }.observes('checked'), + + disabled: function () { + return !this.get('parentView.serviceConfig.isEditable') || + !['addServiceController', 'installerController'].contains(this.get('controller.wizardController.name')) && /^New\s\w+\sDatabase$/.test(this.get('value')); + }.property('parentView.serviceConfig.isEditable') +}); + +App.ServiceConfigComboBox = Ember.Select.extend(App.ServiceConfigPopoverSupport, App.ServiceConfigCalculateId, { + contentBinding: 'serviceConfig.options', + selectionBinding: 'serviceConfig.value', + placeholderBinding: 'serviceConfig.defaultValue', + classNames: [ 'span3' ] +}); + + +/** + * Base component for host config with popover support + */ +App.ServiceConfigHostPopoverSupport = Ember.Mixin.create({ + + /** + * Config object. It will instance of App.ServiceConfigProperty + */ + serviceConfig: null, + + didInsertElement: function () { + App.popover(this.$(), { + title: this.get('serviceConfig.displayName'), + content: this.get('serviceConfig.description'), + placement: 'right', + trigger: 'hover' + }); + } +}); + +/** + * Master host component. + * Show hostname without ability to edit it + * @type {*} + */ +App.ServiceConfigMasterHostView = Ember.View.extend(App.ServiceConfigHostPopoverSupport, App.ServiceConfigCalculateId, { + + classNames: ['master-host', 'span6'], + valueBinding: 'serviceConfig.value', + + template: Ember.Handlebars.compile('{{value}}') + +}); + +/** + * text field property view that enables possibility + * for check connectio + * @type {*} + */ +App.checkConnectionView = App.ServiceConfigTextField.extend({ + didInsertElement: function() { + this._super(); + var kdc = this.get('categoryConfigsAll').findProperty('name', 'kdc_type'); + var propertyAppendTo = this.get('categoryConfigsAll').findProperty('name', 'admin_password'); + if (propertyAppendTo) propertyAppendTo.set('additionalView', App.CheckDBConnectionView.extend({databaseName: kdc && kdc.get('value')})); + } +}); + +/** + * Show value as plain label in italics + * @type {*} + */ +App.ServiceConfigLabelView = Ember.View.extend(App.ServiceConfigHostPopoverSupport, App.ServiceConfigCalculateId, { + + classNames: ['master-host', 'span6'], + valueBinding: 'serviceConfig.value', + + template: Ember.Handlebars.compile('<i>{{view.value}}</i>') +}); + +/** + * Base component to display Multiple hosts + * @type {*} + */ +App.ServiceConfigMultipleHostsDisplay = Ember.Mixin.create(App.ServiceConfigHostPopoverSupport, App.ServiceConfigCalculateId, { + + hasNoHosts: function () { + console.log('view', this.get('viewName')); //to know which View cause errors + console.log('controller', this.get('controller').name); //should be slaveComponentGroupsController + if (!this.get('value')) { + return true; + } + return this.get('value').length === 0; + }.property('value'), + + hasOneHost: function () { + return this.get('value').length === 1; + }.property('value'), + + hasMultipleHosts: function () { + return this.get('value').length > 1; + }.property('value'), + + otherLength: function () { + var len = this.get('value').length; + if (len > 2) { + return Em.I18n.t('installer.controls.serviceConfigMultipleHosts.others').format(len - 1); + } else { + return Em.I18n.t('installer.controls.serviceConfigMultipleHosts.other'); + } + }.property('value') + +}); + + +/** + * Multiple master host component. + * Show hostnames without ability to edit it + * @type {*} + */ +App.ServiceConfigMasterHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, App.ServiceConfigCalculateId, { + + viewName: "serviceConfigMasterHostsView", + valueBinding: 'serviceConfig.value', + + classNames: ['master-hosts', 'span6'], + templateName: require('templates/wizard/master_hosts'), + + /** + * Onclick handler for link + */ + showHosts: function () { + var serviceConfig = this.get('serviceConfig'); + App.ModalPopup.show({ + header: Em.I18n.t('installer.controls.serviceConfigMasterHosts.header').format(serviceConfig.category), + bodyClass: Ember.View.extend({ + serviceConfig: serviceConfig, + templateName: require('templates/wizard/master_hosts_popup') + }), + secondary: null + }); + } + +}); + +/** + * Show tabs list for slave hosts + * @type {*} + */ +App.SlaveComponentGroupsMenu = Em.CollectionView.extend(App.ServiceConfigCalculateId, { + + content: function () { + return this.get('controller.componentGroups'); + }.property('controller.componentGroups'), + + tagName: 'ul', + classNames: ["nav", "nav-tabs"], + + itemViewClass: Em.View.extend({ + classNameBindings: ["active"], + + active: function () { + return this.get('content.active'); + }.property('content.active'), + + errorCount: function () { + return this.get('content.properties').filterProperty('isValid', false).filterProperty('isVisible', true).get('length'); + }.property('content.properties.@each.isValid', 'content.properties.@each.isVisible'), + + templateName: require('templates/wizard/controls_slave_component_groups_menu') + }) + +}); + +/** + * <code>Add group</code> button + * @type {*} + */ +App.AddSlaveComponentGroupButton = Ember.View.extend(App.ServiceConfigCalculateId, { + + tagName: 'span', + slaveComponentName: null, + + didInsertElement: function () { + App.popover(this.$(), { + title: Em.I18n.t('installer.controls.addSlaveComponentGroupButton.title').format(this.get('slaveComponentName')), + content: Em.I18n.t('installer.controls.addSlaveComponentGroupButton.content').format(this.get('slaveComponentName'), this.get('slaveComponentName'), this.get('slaveComponentName')), + placement: 'right', + trigger: 'hover' + }); + } + +}); + +/** + * Multiple Slave Hosts component + * @type {*} + */ +App.ServiceConfigSlaveHostsView = Ember.View.extend(App.ServiceConfigMultipleHostsDisplay, App.ServiceConfigCalculateId, { + + viewName: 'serviceConfigSlaveHostsView', + + classNames: ['slave-hosts', 'span6'], + + valueBinding: 'serviceConfig.value', + + templateName: require('templates/wizard/slave_hosts'), + + /** + * Onclick handler for link + */ + showHosts: function () { + var serviceConfig = this.get('serviceConfig'); + App.ModalPopup.show({ + header: Em.I18n.t('installer.controls.serviceConfigMasterHosts.header').format(serviceConfig.category), + bodyClass: Ember.View.extend({ + serviceConfig: serviceConfig, + templateName: require('templates/wizard/master_hosts_popup') + }), + secondary: null + }); + } + +}); + +/** + * properties for present active slave group + * @type {*} + */ +App.SlaveGroupPropertiesView = Ember.View.extend(App.ServiceConfigCalculateId, { + + viewName: 'serviceConfigSlaveHostsView', + + group: function () { + return this.get('controller.activeGroup'); + }.property('controller.activeGroup'), + + groupConfigs: function () { + console.log("************************************************************************"); + console.log("The value of group is: " + this.get('group')); + console.log("************************************************************************"); + return this.get('group.properties'); + }.property('group.properties.@each').cacheable(), + + errorCount: function () { + return this.get('group.properties').filterProperty('isValid', false).filterProperty('isVisible', true).get('length'); + }.property('configs.@each.isValid', 'configs.@each.isVisible') +}); + +/** + * DropDown component for <code>select hosts for groups</code> popup + * @type {*} + */ +App.SlaveComponentDropDownGroupView = Ember.View.extend(App.ServiceConfigCalculateId, { + + viewName: "slaveComponentDropDownGroupView", + + /** + * On change handler for <code>select hosts for groups</code> popup + * @param event + */ + changeGroup: function (event) { + var host = this.get('content'); + var groupName = $('#' + this.get('elementId') + ' select').val(); + this.get('controller').changeHostGroup(host, groupName); + }, + + optionTag: Ember.View.extend({ + + /** + * Whether current value(OptionTag value) equals to host value(assigned to SlaveComponentDropDownGroupView.content) + */ + selected: function () { + return this.get('parentView.content.group') === this.get('content'); + }.property('content') + }) +}); + +/** + * Show info about current group + * @type {*} + */ +App.SlaveComponentChangeGroupNameView = Ember.View.extend(App.ServiceConfigCalculateId, { + + contentBinding: 'controller.activeGroup', + classNames: ['control-group'], + classNameBindings: 'error', + error: false, + setError: function () { + this.set('error', false); + }.observes('controller.activeGroup'), + errorMessage: function () { + return this.get('error') ? Em.I18n.t('installer.controls.slaveComponentChangeGroupName.error') : ''; + }.property('error'), + + /** + * Onclick handler for saving updated group name + * @param event + */ + changeGroupName: function (event) { + var inputVal = $('#' + this.get('elementId') + ' input[type="text"]').val(); + if (inputVal !== this.get('content.name')) { + var result = this.get('controller').changeSlaveGroupName(this.get('content'), inputVal); + this.set('error', result); + } + } +}); +/** + * View for testing connection to database. + **/ +App.CheckDBConnectionView = Ember.View.extend({ + templateName: require('templates/common/form/check_db_connection'), + /** @property {string} btnCaption - text for button **/ + btnCaption: Em.I18n.t('services.service.config.database.btn.idle'), + /** @property {string} responseCaption - text for status link **/ + responseCaption: null, + /** @property {boolean} isConnecting - is request to server activated **/ + isConnecting: false, + /** @property {boolean} isValidationPassed - check validation for required fields **/ + isValidationPassed: null, + /** @property {string} databaseName- name of current database **/ + databaseName: null, + /** @property {boolean} isRequestResolved - check for finished request to server **/ + isRequestResolved: false, + /** @property {boolean} isConnectionSuccess - check for successful connection to database **/ + isConnectionSuccess: null, + /** @property {string} responseFromServer - message from server response **/ + responseFromServer: null, + /** @property {Object} ambariRequiredProperties - properties that need for custom action request **/ + ambariRequiredProperties: null, + /** @property {Number} currentRequestId - current custom action request id **/ + currentRequestId: null, + /** @property {Number} currentTaskId - current custom action task id **/ + currentTaskId: null, + /** @property {jQuery.Deferred} request - current $.ajax request **/ + request: null, + /** @property {Number} pollInterval - timeout interval for ajax polling **/ + pollInterval: 3000, + /** @property {string} hostNameProperty - host name property based on service and database names **/ + hostNameProperty: function() { + if (!/wizard/i.test(this.get('controller.name')) && this.get('parentView.service.serviceName') === 'HIVE') { + return this.get('parentView.service.serviceName').toLowerCase() + '_hostname'; + } else if (this.get('parentView.service.serviceName') === 'KERBEROS') { + return 'kdc_host'; + } + return '{0}_existing_{1}_host'.format(this.get('parentView.service.serviceName').toLowerCase(), this.get('databaseName').toLowerCase()); + }.property('databaseName'), + /** @property {boolean} isBtnDisabled - disable button on failed validation or active request **/ + isBtnDisabled: function() { + return !this.get('isValidationPassed') || this.get('isConnecting'); + }.property('isValidationPassed', 'isConnecting'), + /** @property {object} requiredProperties - properties that necessary for database connection **/ + requiredProperties: function() { + var propertiesMap = { + OOZIE: ['oozie.db.schema.name','oozie.service.JPAService.jdbc.username','oozie.service.JPAService.jdbc.password','oozie.service.JPAService.jdbc.driver','oozie.service.JPAService.jdbc.url'], + HIVE: ['ambari.hive.db.schema.name','javax.jdo.option.ConnectionUserName','javax.jdo.option.ConnectionPassword','javax.jdo.option.ConnectionDriverName','javax.jdo.option.ConnectionURL'], + KERBEROS: ['kdc_host'] + }; + return propertiesMap[this.get('parentView.service.serviceName')]; + }.property(), + /** @property {Object} propertiesPattern - check pattern according to type of connection properties **/ + propertiesPattern: function() { + var patterns = { + db_connection_url: /jdbc\.url|connectionurl|kdc_host/ig + }; + if (this.get('parentView.service.serviceName') != "KERBEROS") { + patterns.user_name = /(username|dblogin)$/ig; + patterns.user_passwd = /(dbpassword|password)$/ig; + } + return patterns; + }.property('parentView.service.serviceName'), + /** @property {String} masterHostName - host name location of Master Component related to Service **/ + masterHostName: function() { + var serviceMasterMap = { + 'OOZIE': 'oozieserver_host', + 'HDFS': 'hadoop_host', + 'HIVE': 'hive_ambari_host', + 'KERBEROS': 'kdc_host' + }; + return this.get('parentView.categoryConfigsAll').findProperty('name', serviceMasterMap[this.get('parentView.service.serviceName')]).get('value'); + }.property('parentView.service.serviceName', 'parentView.categoryConfigsAll.@each.value'), + /** @property {Object} connectionProperties - service specific config values mapped for custom action request **/ + connectionProperties: function() { + var propObj = {}; + for (var key in this.get('propertiesPattern')) { + propObj[key] = this.getConnectionProperty(this.get('propertiesPattern')[key]); + } + return propObj; + }.property('parentView.categoryConfigsAll.@each.value'), + /** + * Properties that stores in local storage used for handling + * last success connection. + * + * @property {Object} preparedDBProperties + **/ + preparedDBProperties: function() { + var propObj = {}; + for (var key in this.get('propertiesPattern')) { + var propName = this.getConnectionProperty(this.get('propertiesPattern')[key], true); + propObj[propName] = this.get('parentView.categoryConfigsAll').findProperty('name', propName).get('value'); + } + return propObj; + }.property(), + /** Check validation and load ambari properties **/ + didInsertElement: function() { + var kdc = this.get('parentView.categoryConfigsAll').findProperty('name', 'kdc_type'); + if (kdc) { + var name = kdc.get('value') == 'Existing MIT KDC' ? 'KDC' : 'AD'; + App.popover(this.$(), { + title: Em.I18n.t('services.service.config.database.btn.idle'), + content: Em.I18n.t('installer.controls.checkConnection.popover').format(name), + placement: 'right', + trigger: 'hover' + }); + } + this.handlePropertiesValidation(); + this.getAmbariProperties(); + }, + /** On view destroy **/ + willDestroyElement: function() { + this.set('isConnecting', false); + this._super(); + }, + /** + * Observer that take care about enabling/disabling button based on required properties validation. + * + * @method handlePropertiesValidation + **/ + handlePropertiesValidation: function() { + this.restore(); + var isValid = true; + var properties = [].concat(this.get('requiredProperties')); + properties.push(this.get('hostNameProperty')); + properties.forEach(function(propertyName) { + var property = this.get('parentView.categoryConfigsAll').findProperty('name', propertyName); + if(property && !property.get('isValid')) isValid = false; + }, this); + this.set('isValidationPassed', isValid); + }.observes('parentView.categoryConfigsAll.@each.isValid', 'parentView.categoryConfigsAll.@each.value', 'databaseName'), + + getConnectionProperty: function(regexp, isGetName) { + var _this = this; + var propertyName = _this.get('requiredProperties').filter(function(item) { + return regexp.test(item); + })[0]; + return (isGetName) ? propertyName : _this.get('parentView.categoryConfigsAll').findProperty('name', propertyName).get('value'); + }, + /** + * Set up ambari properties required for custom action request + * + * @method getAmbariProperties + **/ + getAmbariProperties: function() { + var clusterController = App.router.get('clusterController'); + var _this = this; + if (!App.isEmptyObject(App.db.get('tmp', 'ambariProperties')) && !this.get('ambariProperties')) { + this.set('ambariProperties', App.db.get('tmp', 'ambariProperties')); + return; + } + if (App.isEmptyObject(clusterController.get('ambariProperties'))) { + clusterController.loadAmbariProperties().done(function(data) { + _this.formatAmbariProperties(data.RootServiceComponents.properties); + }); + } else { + this.formatAmbariProperties(clusterController.get('ambariProperties')); + } + }, + + formatAmbariProperties: function(properties) { + var defaults = { + threshold: "60", + ambari_server_host: location.hostname, + check_execute_list : "db_connection_check" + }; + var properties = App.permit(properties, ['jdk.name','jdk_location','java.home']); + var renameKey = function(oldKey, newKey) { + if (properties[oldKey]) { + defaults[newKey] = properties[oldKey]; + delete properties[oldKey]; + } + }; + renameKey('java.home', 'java_home'); + renameKey('jdk.name', 'jdk_name'); + $.extend(properties, defaults); + App.db.set('tmp', 'ambariProperties', properties); + this.set('ambariProperties', properties); + }, + /** + * `Action` method for starting connect to current database. + * + * @method connectToDatabase + **/ + connectToDatabase: function() { + if (this.get('isBtnDisabled')) return; + this.set('isRequestResolved', false); + App.db.set('tmp', this.get('parentView.service.serviceName') + '_connection', {}); + this.setConnectingStatus(true); + if (App.get('testMode')) { + this.startPolling(); + } else { + this.runCheckConnection(); + } + }, + + /** + * runs check connections methods depending on service + * @return {void} + * @method runCheckConnection + */ + runCheckConnection: function() { + if (this.get('parentView.service.serviceName') === 'KERBEROS') { + this.runKDCCheck(); + } else { + this.createCustomAction(); + } + }, + + /** + * send ajax request to perforn kdc host check + * @return {App.ajax} + * @method runKDCCheck + */ + runKDCCheck: function() { + return App.ajax.send({ + name: 'admin.kerberos_security.test_connection', + sender: this, + data: { + kdcHostname: this.get('masterHostName') + }, + success: 'onRunKDCCheckSuccess', + error: 'onCreateActionError' + }); + }, + + /** + * + * @param data + */ + onRunKDCCheckSuccess: function(data) { + var statusCode = { + success: 'REACHABLE', + failed: 'UNREACHABLE' + }; + if (data == statusCode['success']) { + this.setResponseStatus('success'); + } else { + this.setResponseStatus('failed'); + } + this.set('responseFromServer', data); + }, + + /** + * Run custom action for database connection. + * + * @method createCustomAction + **/ + createCustomAction: function() { + var dbName = this.get('databaseName').toLowerCase() === 'postgresql' ? 'postgres' : this.get('databaseName').toLowerCase(); + var params = $.extend(true, {}, { db_name: dbName }, this.get('connectionProperties'), this.get('ambariProperties')); + App.ajax.send({ + name: 'custom_action.create', + sender: this, + data: { + requestInfo: { + parameters: params + }, + filteredHosts: [this.get('masterHostName')] + }, + success: 'onCreateActionSuccess', + error: 'onCreateActionError' + }); + }, + /** + * Run updater if task is created successfully. + * + * @method onConnectActionS + **/ + onCreateActionSuccess: function(data) { + this.set('currentRequestId', data.Requests.id); + App.ajax.send({ + name: 'custom_action.request', + sender: this, + data: { + requestId: this.get('currentRequestId') + }, + success: 'setCurrentTaskId' + }); + }, + + setCurrentTaskId: function(data) { + this.set('currentTaskId', data.items[0].Tasks.id); + this.startPolling(); + }, + + startPolling: function() { + if (this.get('isConnecting')) + this.getTaskInfo(); + }, + + getTaskInfo: function() { + var request = App.ajax.send({ + name: 'custom_action.request', + sender: this, + data: { + requestId: this.get('currentRequestId'), + taskId: this.get('currentTaskId') + }, + success: 'getTaskInfoSuccess' + }); + this.set('request', request); + }, + + getTaskInfoSuccess: function(data) { + var task = data.Tasks; + this.set('responseFromServer', { + stderr: task.stderr, + stdout: task.stdout + }); + if (task.status === 'COMPLETED') { + var structuredOut = task.structured_out.db_connection_check; + if (structuredOut.exit_code != 0) { + this.set('responseFromServer', { + stderr: task.stderr, + stdout: task.stdout, + structuredOut: structuredOut.message + }); + this.setResponseStatus('failed'); + } else { + App.db.set('tmp', this.get('parentView.service.serviceName') + '_connection', this.get('preparedDBProperties')); + this.setResponseStatus('success'); + } + } + if (task.status === 'FAILED') { + this.setResponseStatus('failed'); + } + if (/PENDING|QUEUED|IN_PROGRESS/.test(task.status)) { + Em.run.later(this, function() { + this.startPolling(); + }, this.get('pollInterval')); + } + }, + + onCreateActionError: function(jqXhr, status, errorMessage) { + this.setResponseStatus('failed'); + this.set('responseFromServer', errorMessage); + }, + + setResponseStatus: function(isSuccess) { + var isSuccess = isSuccess == 'success'; + this.setConnectingStatus(false); + this.set('responseCaption', isSuccess ? Em.I18n.t('services.service.config.database.connection.success') : Em.I18n.t('services.service.config.database.connection.failed')); + this.set('isConnectionSuccess', isSuccess); + this.set('isRequestResolved', true); + }, + /** + * Switch captions and statuses for active/non-active request. + * + * @method setConnectionStatus + * @param {Boolean} [active] + */ + setConnectingStatus: function(active) { + if (active) { + this.set('responseCaption', Em.I18n.t('services.service.config.database.connection.inProgress')); + } + this.set('controller.testConnectionInProgress', !!active); + this.set('btnCaption', !!active ? Em.I18n.t('services.service.config.database.btn.connecting') : Em.I18n.t('services.service.config.database.btn.idle')); + this.set('isConnecting', !!active); + }, + /** + * Set view to init status. + * + * @method restore + **/ + restore: function() { + if (this.get('request')) { + this.get('request').abort(); + this.set('request', null); + } + this.set('responseCaption', null); + this.set('responseFromServer', null); + this.setConnectingStatus(false); + this.set('isRequestResolved', false); + }, + /** + * `Action` method for showing response from server in popup. + * + * @method showLogsPopup + **/ + showLogsPopup: function() { + if (this.get('isConnectionSuccess')) return; + var _this = this; + var popup = App.showAlertPopup('Error: {0} connection'.format(this.get('databaseName'))); + if (typeof this.get('responseFromServer') == 'object') { + popup.set('bodyClass', Em.View.extend({ + templateName: require('templates/common/error_log_body'), + openedTask: _this.get('responseFromServer') + })); + } else { + popup.set('body', this.get('responseFromServer')); + } + return popup; + } +}); + +/** + * + * @type {*} + */ +App.BaseUrlTextField = Ember.TextField.extend({ + + valueBinding: 'repository.baseUrl', + + keyUp: function (event) { + if (Em.get(this, 'repository.hasError')) { + Em.set(this, 'repository.hasError', false); + } + } + +}); + http://git-wip-us.apache.org/repos/asf/ambari/blob/11efa2f5/ambari-web/app/views/main/admin/stack_upgrade/upgrade_version_box_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/admin/stack_upgrade/upgrade_version_box_view.js b/ambari-web/app/views/main/admin/stack_upgrade/upgrade_version_box_view.js index cce4790..505c3bb 100644 --- a/ambari-web/app/views/main/admin/stack_upgrade/upgrade_version_box_view.js +++ b/ambari-web/app/views/main/admin/stack_upgrade/upgrade_version_box_view.js @@ -129,6 +129,7 @@ App.UpgradeVersionBoxView = Em.View.extend({ var repoRecord = App.RepositoryVersion.find(this.get('content.id')); //make deep copy of repoRecord var repo = Em.Object.create({ + repoVersionId: repoRecord.get('id'), displayName: repoRecord.get('displayName'), repositoryVersion: repoRecord.get('displayName'), operatingSystems: repoRecord.get('operatingSystems').map(function (os) { @@ -140,7 +141,8 @@ App.UpgradeVersionBoxView = Em.View.extend({ return Em.Object.create({ repoName: repository.get('repoName'), repoId: repository.get('repoId'), - baseUrl: repository.get('baseUrl') + baseUrl: repository.get('baseUrl'), + hasError: false }); }) }); @@ -149,20 +151,48 @@ App.UpgradeVersionBoxView = Em.View.extend({ return App.ModalPopup.show({ classNames: ['repository-list', 'sixty-percent-width-modal'], + skipValidation: false, + hasErrors: false, bodyClass: Ember.View.extend({ content: repo, + skipCheckBox: Ember.Checkbox.extend({ + classNames: ["align-checkbox"], + change: function() { + this.get('parentView.content.operatingSystems').forEach(function(os) { + if (Em.get(os, 'repositories.length') > 0) { + os.get('repositories').forEach(function(repo) { + Em.set(repo, 'hasError', false); + }) + } + }); + } + }), templateName: require('templates/main/admin/stack_upgrade/edit_repositories'), - skipValidation: false, didInsertElement: function () { App.tooltip($("[rel=skip-validation-tooltip]"), {placement: 'right'}); - } + }, + OSCheckBox: Ember.Checkbox.extend({ + classNames: ["align-checkbox"], + + checkedBinding: "os.isSelected", + + change: function () { + this.get('os.repositories').setEach('hasError', false); + } + }) }), header: Em.I18n.t('common.repositories'), primary: Em.I18n.t('common.save'), disablePrimary: !(App.get('isAdmin') && !App.get('isOperator')), onPrimary: function () { - this.hide(); - self.get('controller').saveRepoOS(); + var self = this; + App.get('router.mainAdminStackAndUpgradeController').saveRepoOS(repo, this.get('skipValidation')).done(function(data){ + if (data.length > 0) { + self.set('hasErrors', true); + } else { + self.hide(); + } + }) } }); },