Repository: ambari Updated Branches: refs/heads/trunk 742f5af65 -> 8b1d76cdf
AMBARI-5715. Usability: Ability to edit local repo urls in the UI - post install.(xiwang) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8b1d76cd Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8b1d76cd Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8b1d76cd Branch: refs/heads/trunk Commit: 8b1d76cdf4e0d8e54ba157411d997a4e1e00bc3d Parents: 742f5af Author: Xi Wang <[email protected]> Authored: Fri May 16 11:35:25 2014 -0700 Committer: Xi Wang <[email protected]> Committed: Mon May 19 11:50:47 2014 -0700 ---------------------------------------------------------------------- .../app/controllers/main/admin/cluster.js | 7 +- ambari-web/app/messages.js | 9 + ambari-web/app/styles/application.less | 114 ++++++++++ ambari-web/app/templates/common/modal_popup.hbs | 3 + ambari-web/app/templates/main/admin/cluster.hbs | 80 +++++-- ambari-web/app/views/common/modal_popup.js | 6 + ambari-web/app/views/main/admin/cluster.js | 218 ++++++++++++++++++- 7 files changed, 411 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/controllers/main/admin/cluster.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/cluster.js b/ambari-web/app/controllers/main/admin/cluster.js index 7a348b1..6e105da 100644 --- a/ambari-web/app/controllers/main/admin/cluster.js +++ b/ambari-web/app/controllers/main/admin/cluster.js @@ -22,7 +22,7 @@ var stringUtils = require('utils/string_utils'); App.MainAdminClusterController = Em.Controller.extend({ name:'mainAdminClusterController', services: [], - repositories: [], + allRepos: [], upgradeVersion: '', /** * get the newest version of HDP from server @@ -98,6 +98,9 @@ App.MainAdminClusterController = Em.Controller.extend({ baseUrl: repository.Repositories.base_url, osType: osType, repoId: repository.Repositories.repo_id, + repoName : repository.Repositories.repo_name, + stackName : repository.Repositories.stack_name, + stackVersion : repository.Repositories.stack_version, isFirst: false }); var group = allRepos.findProperty('name', osType); @@ -113,7 +116,7 @@ App.MainAdminClusterController = Em.Controller.extend({ }); }, this); allRepos.stackVersion = App.get('currentStackVersionNumber'); - this.set('repositories', allRepos); + this.set('allRepos', allRepos); }, loadRepositoriesErrorCallback: function(request, ajaxOptions, error) { http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index ca273c7..7df4930 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -116,6 +116,7 @@ Em.I18n.translations = { 'common.component':'Component', 'common.quickLinks':'Quick Links', 'common.save':'Save', + 'common.saveAnyway':'Save Anyway', 'common.servers':'Servers', 'common.clients':'Clients', 'common.user': 'User', @@ -163,6 +164,7 @@ Em.I18n.translations = { 'common.startTime': 'Start Time', 'common.duration': 'Duration', 'common.reinstall': 'Re-Install', + 'common.revert': 'Revert', 'common.errorPopup.header': 'An error has been encountered', 'common.use': 'Use', 'common.stacks': 'Stacks', @@ -1024,6 +1026,13 @@ Em.I18n.translations = { 'admin.cluster.repositories.repositories':'Repositories', 'admin.cluster.repositories.os':'OS', 'admin.cluster.repositories.baseUrl':'Base URL', + 'admin.cluster.repositories.popup.header.success':'Repo Base URLs Saved', + 'admin.cluster.repositories.popup.header.fail':'Base URL Validation Failed', + 'admin.cluster.repositories.popup.body.success':'The Repo Base URL has been saved successfully.', + 'admin.cluster.repositories.popup.body.fail':'The Base URL failed validation and has not been saved.', + 'admin.cluster.repositories.invalidURLAttention': '<b>Attention:</b> Please make sure all repository URLs are valid before saving.', + 'admin.cluster.repositories.emptyURLattention':'<b>Attention:</b> Repository URLs are REQUIRED before you can save.', + 'admin.cluster.repositories.skipValidation.tooltip':'<b>Warning:</b> This is for advanced users only. Use this option if you want to skip validation for Repository Base URLs.', 'admin.misc.header': 'Service Users and Groups', 'admin.misc.nothingToShow': 'No user accounts to display', http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/styles/application.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less index a251225..aac13a4 100644 --- a/ambari-web/app/styles/application.less +++ b/ambari-web/app/styles/application.less @@ -5853,6 +5853,8 @@ i.icon-asterisks { } .accordion-body { .repositories-table { + overflow: auto; + margin-bottom: 10px; div { float: left; min-height: 1px; @@ -5954,6 +5956,118 @@ i.icon-asterisks { } } +.admin-cluster { + .repositories-table { + margin-bottom: 10px; + border: 1px solid #dddddd; + overflow: auto; + div { + float: left; + min-height: 1px; + } + .thead { + width: 100%; + .th { + font-weight: bold; + padding: 8px; + } + .os-th { + width: 10%; + } + .name-th { + width: 16%; + } + .url-th { + width: 66%; + } + } + .tbody { + width: 100%; + .trow { + width: 100%; + border-top: 1px solid #dddddd; + padding-top: 8px; + .os-td { + padding-top: 4px; + padding-left: 8px; + width: 9%; + } + .sub-trow { + width: 100%; + min-height: 39px; + .name-td { + width: 16%; + padding-top: 4px; + } + .url-td { + width: 60%; + .ember-text-field { + width: 100%; + } + } + .url-text-td { + width: 70%; + padding-top: 4px; + padding-left: 3px; + overflow: scroll; + } + .edit-td { + width: 8%; + padding-top: 4px; + padding-left: 5px; + a { + cursor: pointer; + } + } + .edit-buttons-td { + // save or cancel + width: 9%; + } + .clear-td { + width: 3%; + padding-top: 5px; + padding-left: 12px; + a { + cursor: pointer; + text-decoration: none; + } + .icon-remove-sign { + color: #808080; + } + } + } + } + } + .textfield-error input { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + } + .disabled-textfield input { + color: #808080; + disabled: disabled; + pointer-events: none; + cursor: default; + background: #E4E4E4; + } + .disabled-label { + color: #808080; + } + } + #skip-validation { + margin-top: 10px; + .checkbox { + margin-left: 3px; + margin-right: 8px; + margin-top: 0px; + } + .icon-question-sign { + color: @blue; + } + } +} + #config-group-select-create-dialog { .select-create-config-group-div { margin-left: 20px; http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/templates/common/modal_popup.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/common/modal_popup.hbs b/ambari-web/app/templates/common/modal_popup.hbs index 65a95d7..c2326d4 100644 --- a/ambari-web/app/templates/common/modal_popup.hbs +++ b/ambari-web/app/templates/common/modal_popup.hbs @@ -51,6 +51,9 @@ <label id="footer-checkbox">{{view Ember.Checkbox classNames="checkbox" checkedBinding="view.isNotShowBgChecked"}} {{t app.settings.notShowBgOperations}}</label> {{/if}} + {{#if view.third}} + <button class="btn" {{bindAttr disabled="view.disableThird"}} {{action onThird target="view"}}>{{view.third}}</button> + {{/if}} {{#if view.secondary}} <button class="btn" {{bindAttr disabled="view.disableSecondary"}} {{action onSecondary target="view"}}>{{view.secondary}}</button> {{/if}} http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/templates/main/admin/cluster.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/admin/cluster.hbs b/ambari-web/app/templates/main/admin/cluster.hbs index 771b261..b38b28c 100644 --- a/ambari-web/app/templates/main/admin/cluster.hbs +++ b/ambari-web/app/templates/main/admin/cluster.hbs @@ -56,30 +56,66 @@ </div> <ul class="nav nav-tabs"> <li class="active"> - <a href="javascript:void(null);">{{repositories.stackVersion}}</a> + <a href="javascript:void(null);">{{view.allRepositoriesGroups.stackVersion}}</a> </li> </ul> - <table class="table table-bordered"> - <thead> - <tr> - <th>{{t admin.cluster.repositories.os}}</th> - <th>{{t common.name}}</th> - <th>{{t admin.cluster.repositories.baseUrl}}</th> - </tr> - </thead> - <tbody> - {{#each repoGroup in repositories}} - {{#each repo in repoGroup.repositories}} - <tr> - {{#if repo.isFirst}} - <td {{bindAttr rowspan="repoGroup.repositories.length"}}>{{repo.osType}}</td> - {{/if}} - <td>{{repo.repoId}}</td> - <td>{{repo.baseUrl}}</td> - </tr> - {{/each}} + <div class="repositories-table"> + <div class="thead"> + <div class="th os-th">{{t common.os}}</div> + <div class="th name-th">{{t common.name}}</div> + <div class="th url-th">{{t installer.step1.advancedRepo.localRepo.column.baseUrl}}</div> + </div> + <div class="tbody"> + {{#each repoGroup in view.allRepositoriesGroups}} + <div class="trow"> + <div class="os-td"> + <label> + <span class="os">{{repoGroup.name}}</span> + </label> + </div> + <div style="width:89%"> + {{#each repository in repoGroup.repositories}} + <div class="sub-trow"> + <div class="name-td">{{repository.repoId}}</div> + <!--edit mode for current url--> + {{#if repository.onEdit}} + <div {{bindAttr class=":url-td repository.empty-error:textfield-error repository.invalid-error:textfield-error"}}> + {{view Ember.TextField valueBinding="repository.baseUrl"}} + </div> + <div class="clear-td"> + {{#if repository.clearAll}} + <a {{action "clearGroupLocalRepository" repository target="view" }}> + <i class="icon-remove-sign"></i> + </a> + {{/if}} + </div> + <div class="edit-buttons-td"> + <a class="btn" {{action doCancel repository target="view"}}>{{t common.cancel}}</a> + </div> + <div class="edit-buttons-td"> + {{#if repository.empty-error}} + <a class="btn btn-primary" disabled="disabled">{{t common.save}}</a> + {{else}} + <a class="btn btn-primary" {{action saveRepoUrls repository target="view"}}>{{t common.save}}</a> + {{/if}} + </div> + <!--non-edit mode for current url--> + {{else}} + <div class="url-text-td"> + {{repository.baseUrl}} + </div> + <div class="edit-td"> + <a {{action "onEditClick" repository target="view" }}> + <i class="icon-edit"></i> {{t common.edit}} + </a> + </div> + {{/if}} + </div> + {{/each}} + </div> + </div> {{/each}} - </tbody> - </table> + </div> + </div> </div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/views/common/modal_popup.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/modal_popup.js b/ambari-web/app/views/common/modal_popup.js index 9be6050..550af3c 100644 --- a/ambari-web/app/views/common/modal_popup.js +++ b/ambari-web/app/views/common/modal_popup.js @@ -28,9 +28,11 @@ App.ModalPopup = Ember.View.extend({ // define bodyClass which extends Ember.View to use an arbitrary Handlebars template as the body primary: Em.I18n.t('ok'), secondary: Em.I18n.t('common.cancel'), + third: null, autoHeight: true, disablePrimary: false, disableSecondary: false, + disableThird: false, primaryClass: 'btn-success', onPrimary: function () { this.hide(); @@ -40,6 +42,10 @@ App.ModalPopup = Ember.View.extend({ this.hide(); }, + onThird: function () { + this.hide(); + }, + onClose: function () { this.hide(); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/8b1d76cd/ambari-web/app/views/main/admin/cluster.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/admin/cluster.js b/ambari-web/app/views/main/admin/cluster.js index 3c26f03..44ae19b 100644 --- a/ambari-web/app/views/main/admin/cluster.js +++ b/ambari-web/app/views/main/admin/cluster.js @@ -24,5 +24,219 @@ App.MainAdminClusterView = Em.View.extend({ isUpgradeAvailable: function(){ return stringUtils.compareVersions(this.get('controller.upgradeVersion').replace(/HDP(Local)?-/, ''), App.get('currentStackVersionNumber')) === 1; - }.property('controller.upgradeVersion', 'App.currentStackVersion') -}); \ No newline at end of file + }.property('controller.upgradeVersion', 'App.currentStackVersion'), + + didInsertElement: function () { + this.get('controller').loadRepositories(); + }, + + /** + * List of all repo-groups + * @type {Object[][]} + */ + allRepositoriesGroups: function () { + var repos = this.get('controller.allRepos'); + var reposGroup = []; + var repositories = []; + reposGroup.set('stackVersion', App.get('currentStackVersionNumber')); + if (repos) { + repos.forEach(function (group) { + group.repositories.forEach (function(repo) { + var cur_repo = Em.Object.create({ + 'repoId': repo.repoId, + 'id': repo.repoId + '-' + repo.osType, + 'repoName' : repo.repoName, + 'stackName' : repo.stackName, + 'stackVersion' : repo.stackVersion, + 'baseUrl': repo.baseUrl, + 'originalBaseUrl': repo.baseUrl, + 'osType': repo.osType, + 'onEdit': false, + 'empty-error': !repo.baseUrl, + 'undo': false, + 'clearAll': repo.baseUrl + }); + var cur_group = reposGroup.findProperty('name', group.name); + if (!cur_group) { + var cur_group = Ember.Object.create({ + name: group.name, + repositories: [] + }); + reposGroup.push(cur_group); + } + cur_group.repositories.push(cur_repo); + repositories.push(cur_repo); + }); + }); + } + this.set('allRepos', repositories); + return reposGroup; + }.property('controller.allRepos'), + + /** + * Onclick handler for edit action of each repo, enter edit mode + * @param {object} event + */ + onEditClick:function (event) { + var targetRepo = this.get('allRepos').findProperty('id', event.context.get('id')); + if (targetRepo) { + targetRepo.set('onEdit', true); + } + }, + + /** + * Onclick handler for undo action of each repo group + * @method undoGroupLocalRepository + * @param {object} event + */ + undoGroupLocalRepository: function (event) { + this.doActionForGroupLocalRepository(event, 'originalBaseUrl'); + }, + + /** + * Handler for clear icon click + * @method clearGroupLocalRepository + * @param {object} event + */ + clearGroupLocalRepository: function (event) { + this.doActionForGroupLocalRepository(event, ''); + }, + + /** + * Common handler for repo groups actions + * @method doActionForGroupLocalRepository + * @param {object} event + * @param {string} newBaseUrlField + */ + doActionForGroupLocalRepository: function (event, newBaseUrlField) { + var targetRepo = this.get('allRepos').findProperty('id', event.context.get('id')); + if (targetRepo) { + targetRepo.set('baseUrl', Em.isEmpty(newBaseUrlField) ? '' : Em.get(targetRepo, newBaseUrlField)); + } + }, + + /** + * Handler when editing any repo group BaseUrl + * @method editGroupLocalRepository + */ + editGroupLocalRepository: function (event) { + var repos = this.get('allRepos'); + repos.forEach(function (targetRepo) { + targetRepo.set('undo', targetRepo.get('baseUrl') != targetRepo.get('originalBaseUrl')); + targetRepo.set('clearAll', targetRepo.get('baseUrl')); + targetRepo.set('empty-error', !targetRepo.get('baseUrl')); + + }); + }.observes('[email protected]'), + + /** + * onSuccess callback for save Repo URL. + */ + doSaveRepoUrlsSuccessCallback: function (response, request, data) { + var id = data.repoId + '-' + data.osType; + console.log('Success in check Repo URL. data repoId+osType: ' + id); + var targetRepo = this.get('allRepos').findProperty('id', id); + if (!targetRepo) { + return; + } else { + App.ModalPopup.show({ + header: Em.I18n.t('admin.cluster.repositories.popup.header.success'), + secondary: null, + onPrimary: function () { + this.hide(); + targetRepo.set('baseUrl', data.data.Repositories.base_url); + targetRepo.set('originalBaseUrl', data.data.Repositories.base_url); + targetRepo.set('onEdit', false); + }, + message: Em.I18n.t('admin.cluster.repositories.popup.body.success'), + bodyClass: Em.View.extend({ + template: Em.Handlebars.compile('<div class="alert alert-success">{{{message}}}</div>') + }) + }) + } + }, + + /** + * onError callback for save Repo URL. + */ + doSaveRepoUrlsErrorCallback: function (request, ajaxOptions, error, data) { + console.log('Error in check Repo URL. The baseURL sent is: ' + data.data); + var self = this; + var id = data.url.split('/')[10] + '-' + data.url.split('/')[8]; + var targetRepo = this.get('allRepos').findProperty('id', id); + if (!targetRepo) { + return; + } else { + App.ModalPopup.show({ + header: Em.I18n.t('admin.cluster.repositories.popup.header.fail'), + primary: Em.I18n.t('common.saveAnyway'), + secondary: Em.I18n.t('common.revert'), + third: Em.I18n.t('common.cancel'), + onPrimary: function () { + // save anyway: Go ahead and save with Repo URL validation turned off and close Dialog when done. + this.hide(); + self.doSaveRepoUrls(id, false); + }, + onSecondary: function () { + // Revert: Close dialog, revert URL value, go back to non-Edit mode + this.hide(); + targetRepo.set('baseUrl', targetRepo.get('originalBaseUrl')); + targetRepo.set('onEdit', false); + }, + onThird: function () { + // cancel: Close dialog but stay in Edit mode + this.hide(); + }, + message: Em.I18n.t('admin.cluster.repositories.popup.body.fail'), + bodyClass: Em.View.extend({ + template: Em.Handlebars.compile('<div class="alert alert-warning">{{{message}}}</div>') + }) + }) + } + }, + + /** + * Check validation and Save the customized local urls + */ + doSaveRepoUrls: function (id, verifyBaseUrl) { + var targetRepo = this.get('allRepos').findProperty('id', id); + var verifyBaseUrl = verifyBaseUrl; + App.ajax.send({ + name: 'wizard.advanced_repositories.valid_url', + sender: this, + data: { + stackName: targetRepo.stackName, + stackVersion: targetRepo.stackVersion, + repoId: targetRepo.repoId, + osType: targetRepo.osType, + data: { + 'Repositories': { + 'base_url': targetRepo.baseUrl, + "verify_base_url": verifyBaseUrl + } + } + }, + success: 'doSaveRepoUrlsSuccessCallback', + error: 'doSaveRepoUrlsErrorCallback' + }); + }, + /** + * Check validation and Save the customized local urls + */ + saveRepoUrls: function (event) { + this.doSaveRepoUrls(event.context.get('id'), true); + }, + + /** + * on click handler 'Cancel' for current repo in edit mode + */ + doCancel: function (event) { + var targetRepo = this.get('allRepos').findProperty('id', event.context.get('id')); + if (targetRepo) { + targetRepo.set('baseUrl', targetRepo.get('originalBaseUrl')); + targetRepo.set('onEdit', false); + } + } + +}); +
