This is an automated email from the ASF dual-hosted git repository. ababiichuk pushed a commit to branch branch-2.7 in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.7 by this push: new 22c18e5 AMBARI-24362 Fixes for modal with config validations and dependent properties. (ababiichuk) 22c18e5 is described below commit 22c18e59a5dd067075d1e407a533297cbd97aefe Author: ababiichuk <ababiic...@hortonworks.com> AuthorDate: Thu Jul 26 19:42:48 2018 +0300 AMBARI-24362 Fixes for modal with config validations and dependent properties. (ababiichuk) --- ambari-web/app/controllers/main/host/details.js | 184 ++++++++++++--------- ambari-web/app/controllers/main/service/item.js | 4 +- .../common/configs/config_recommendations.js | 12 +- ambari-web/app/styles/application.less | 33 +++- ambari-web/app/styles/bootstrap_overrides.less | 2 +- ambari-web/app/styles/common.less | 13 ++ ambari-web/app/styles/modal_popups.less | 13 +- ambari-web/app/styles/top-nav.less | 4 +- .../templates/common/configs/services_config.hbs | 4 +- .../modal_popups/config_recommendation_popup.hbs | 40 ++--- .../common/modal_popups/dependent_configs_list.hbs | 6 +- .../modal_popups/dependent_configs_table.hbs | 24 +-- ambari-web/app/views/common/modal_popup.js | 13 +- .../config_validation/config_validation_popup.js | 4 +- .../modal_popups/dependent_configs_list_popup.js | 45 +++-- .../common/modal_popups/log_file_search_popup.js | 2 +- .../common/configs/config_recommendations_test.js | 22 ++- 17 files changed, 269 insertions(+), 156 deletions(-) diff --git a/ambari-web/app/controllers/main/host/details.js b/ambari-web/app/controllers/main/host/details.js index d2c9aca..8a445dd 100644 --- a/ambari-web/app/controllers/main/host/details.js +++ b/ambari-web/app/controllers/main/host/details.js @@ -540,9 +540,9 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow header: Em.I18n.t('popup.confirmation.commonHeader'), controller: this, hasPropertiesToChange: false, - classNameBindings: ['controller.hasPropertiesToChange:common-modal-wrapper', 'controller.hasPropertiesToChange:modal-full-width'], + classNameBindings: ['controller.hasPropertiesToChange:common-modal-wrapper'], modalDialogClasses: function () { - return this.get('controller.hasPropertiesToChange') ? ['modal-lg'] : []; + return this.get('controller.hasPropertiesToChange') ? ['modal-xlg'] : []; }.property('controller.hasPropertiesToChange'), primary: Em.I18n.t('hosts.host.deleteComponent.popup.confirm'), bodyClass: this.get('addDeleteComponentPopupBody').extend({ @@ -808,9 +808,9 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow header: Em.I18n.t('popup.confirmation.commonHeader'), controller: self, hasPropertiesToChange: false, - classNameBindings: ['hasPropertiesToChange:common-modal-wrapper', 'hasPropertiesToChange:modal-full-width'], + classNameBindings: ['hasPropertiesToChange:common-modal-wrapper'], modalDialogClasses: function () { - return this.get('controller.hasPropertiesToChange') ? ['modal-lg'] : []; + return this.get('controller.hasPropertiesToChange') ? ['modal-xlg'] : []; }.property('controller.hasPropertiesToChange'), primary: Em.I18n.t('hosts.host.addComponent.popup.confirm'), bodyClass: self.get('addDeleteComponentPopupBody').extend({ @@ -990,19 +990,19 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow * @method updateZkConfigs */ updateZkConfigs: function (configs) { - var portValue = configs['zoo.cfg'] && Em.get(configs['zoo.cfg'], 'clientPort'); - var zkPort = typeof portValue === 'undefined' ? '2181' : portValue; - var infraSolrZnode = configs['infra-solr-env'] ? Em.get(configs['infra-solr-env'], 'infra_solr_znode') : '/ambari-solr'; - var initializer = App.AddZooKeeperComponentsInitializer; - var hostComponentsTopology = { - masterComponentHosts: [] - }; - var propertiesToChange = this.get('allPropertiesToChange'); - var masterComponents = this.bootstrapHostsMapping('ZOOKEEPER_SERVER'); + const portValue = configs['zoo.cfg'] && Em.get(configs['zoo.cfg'], 'clientPort'), + zkPort = typeof portValue === 'undefined' ? '2181' : portValue, + infraSolrZnode = configs['infra-solr-env'] ? Em.get(configs['infra-solr-env'], 'infra_solr_znode') : '/ambari-solr', + initializer = App.AddZooKeeperComponentsInitializer, + hostComponentsTopology = { + masterComponentHosts: [] + }, + propertiesToChange = this.get('allPropertiesToChange'), + masterComponents = this.bootstrapHostsMapping('ZOOKEEPER_SERVER'); if (this.get('fromDeleteHost') || this.get('fromDeleteZkServer')) { this.set('fromDeleteHost', false); this.set('fromDeleteZkServer', false); - var removedHost = masterComponents.findProperty('hostName', this.get('content.hostName')); + let removedHost = masterComponents.findProperty('hostName', this.get('content.hostName')); if (!Em.isNone(removedHost)) { Em.set(removedHost, 'isInstalled', false); } @@ -1014,34 +1014,38 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow isInstalled: true }); } - var dependencies = { + const dependencies = { zkClientPort: zkPort, - infraSolrZnode: infraSolrZnode + infraSolrZnode }; hostComponentsTopology.masterComponentHosts = masterComponents; - Em.keys(configs).forEach(function(fileName) { - var properties = configs[fileName]; - Em.keys(properties).forEach(function(propertyName) { - var currentValue = properties[propertyName], + Em.keys(configs).forEach(fileName => { + const properties = configs[fileName]; + Em.keys(properties).forEach(propertyName => { + const currentValue = properties[propertyName], propertyDef = { - fileName: fileName, + fileName, name: propertyName, value: currentValue }, configProperty = initializer.initialValue(propertyDef, hostComponentsTopology, dependencies); initializer.updateSiteObj(configs[fileName], configProperty); if (currentValue !== configs[fileName][propertyName]) { - var service = App.config.get('serviceByConfigTypeMap')[fileName]; + const service = App.config.get('serviceByConfigTypeMap')[fileName], + configObject = App.configsCollection.getConfigByName(propertyName, fileName), + displayName = configObject && configObject.displayName; propertiesToChange.pushObject({ propertyFileName: fileName, - propertyName: propertyName, + propertyName, + propertyTitle: configObject && Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, displayName === propertyName ? '' : propertyName), + propertyDescription: configObject && configObject.description, serviceDisplayName: service && service.get('displayName'), initialValue: currentValue, recommendedValue: propertyDef.value }); } - }, this); - }, this); + }); + }); }, /** @@ -1069,40 +1073,46 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow * @method onLoadStormConfigs */ onLoadStormConfigs: function (data) { - var nimbusHost = this.get('nimbusHost'), + const nimbusHost = this.get('nimbusHost'), stormNimbusHosts = this.getStormNimbusHosts(), configs = {}, attributes = {}, propertiesToChange = this.get('allPropertiesToChange'); this.saveLoadedConfigs(data); - data.items.forEach(function (item) { + data.items.forEach(item => { configs[item.type] = item.properties; attributes[item.type] = item.properties_attributes || {}; - }, this); + }); this.updateZkConfigs(configs); - var nimbusSeedsInit = configs['storm-site']['nimbus.seeds'], + const propertyName = 'nimbus.seeds', + propertyFileName = 'storm-site', + nimbusSeedsInit = configs[propertyFileName][propertyName], nimbusSeedsRecommended = JSON.stringify(stormNimbusHosts).replace(/"/g, "'"); - configs['storm-site']['nimbus.seeds'] = nimbusSeedsRecommended; + configs[propertyFileName][propertyName] = nimbusSeedsRecommended; if (this.get('isReconfigureRequired') && nimbusSeedsInit !== nimbusSeedsRecommended) { - var service = App.config.get('serviceByConfigTypeMap')['storm-site']; + const service = App.config.get('serviceByConfigTypeMap')[propertyFileName], + configObject = App.configsCollection.getConfigByName(propertyName, propertyFileName), + displayName = configObject && configObject.displayName; propertiesToChange.pushObject({ - propertyFileName: 'storm-site', - propertyName: 'nimbus.seeds', + propertyFileName, + propertyName, + propertyTitle: configObject && Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, displayName === propertyName ? '' : propertyName), + propertyDescription: configObject && configObject.description, serviceDisplayName: service && service.get('displayName'), initialValue: nimbusSeedsInit, recommendedValue: nimbusSeedsRecommended }); } - var groups = [ + const groups = [ { properties: { - 'storm-site': configs['storm-site'] + [propertyFileName]: configs[propertyFileName] }, properties_attributes: { - 'storm-site': attributes['storm-site'] + [propertyFileName]: attributes[propertyFileName] } } ]; @@ -1110,41 +1120,45 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow }, onLoadAtlasConfigs: function(data) { - var atlasServer = this.get('atlasServer'), + const atlasServer = this.get('atlasServer'), atlasServerHosts = this.getAtlasServerHosts(), configs = {}, attributes = {}, propertiesToChange = this.get('allPropertiesToChange'); this.saveLoadedConfigs(data); - data.items.forEach(function (item) { + data.items.forEach(item => { configs[item.type] = item.properties; attributes[item.type] = item.properties_attributes || {}; - }, this); + }); - var atlasAddresses = configs['application-properties']['atlas.rest.address']; - var hostMask = atlasAddresses.split(',')[0].replace(/([https|http]*\:\/\/)(.*?)(:[0-9]+)/, '$1{hostname}$3'); - var atlasAddressesRecommended = atlasServerHosts.map(function(hostName) { - return hostMask.replace('{hostname}', hostName); - }).join(','); - configs['application-properties']['atlas.rest.address'] = atlasAddressesRecommended; + const propertyFileName = 'application-properties', + propertyName = 'atlas.rest.address', + atlasAddresses = configs[propertyFileName][propertyName], + hostMask = atlasAddresses.split(',')[0].replace(/([https|http]*\:\/\/)(.*?)(:[0-9]+)/, '$1{hostname}$3'), + atlasAddressesRecommended = atlasServerHosts.map(hostName => hostMask.replace('{hostname}', hostName)).join(','); + configs[propertyFileName][propertyName] = atlasAddressesRecommended; if (this.get('isReconfigureRequired') && atlasAddresses !== atlasAddressesRecommended) { - var service = App.config.get('serviceByConfigTypeMap')['application-properties']; + var service = App.config.get('serviceByConfigTypeMap')[propertyFileName], + configObject = App.configsCollection.getConfigByName(propertyName, propertyFileName), + displayName = configObject && configObject.displayName; propertiesToChange.pushObject({ - propertyFileName: 'application-properties', - propertyName: 'atlas.rest.address', + propertyFileName, + propertyName, + propertyTitle: configObject && Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, displayName === propertyName ? '' : propertyName), + propertyDescription: configObject && configObject.description, serviceDisplayName: service && service.get('displayName'), initialValue: atlasAddresses, recommendedValue: atlasAddressesRecommended }); } - var groups = [ + const groups = [ { properties: { - 'application-properties': configs['application-properties'] + [propertyFileName]: configs[propertyFileName] }, properties_attributes: { - 'application-properties': attributes['application-properties'] + [propertyFileName]: attributes[propertyFileName] } } ]; @@ -1202,26 +1216,26 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow * @method onLoadHiveConfigs */ onLoadHiveConfigs: function (data, opt, params) { - var port = ""; - var configs = {}; - var attributes = {}; - var userSetup = {}; - var localDB = { - masterComponentHosts: this.getHiveHosts() - }; - var dependencies = { - hiveMetastorePort: "" - }; - var initializer = params.webHCat ? App.AddWebHCatComponentsInitializer : App.AddHiveComponentsInitializer; + let port = ""; + const configs = {}, + attributes = {}, + userSetup = {}, + localDB = { + masterComponentHosts: this.getHiveHosts() + }, + dependencies = { + hiveMetastorePort: "" + }, + initializer = params.webHCat ? App.AddWebHCatComponentsInitializer : App.AddHiveComponentsInitializer; this.saveLoadedConfigs(data); this.set('configs.params', { webHCat: params.webHCat }); - data.items.forEach(function (item) { + data.items.forEach(item => { configs[item.type] = item.properties; attributes[item.type] = item.properties_attributes || {}; - }, this); - var propertiesToChange = this.get('allPropertiesToChange'); + }); + const propertiesToChange = this.get('allPropertiesToChange'); port = configs['hive-site']['hive.metastore.uris'].match(/:[0-9]{2,4}/); port = port ? port[0].slice(1) : "9083"; @@ -1236,30 +1250,34 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow initializer.setup(userSetup); - ['hive-site', 'webhcat-site', 'hive-env', 'core-site'].forEach(function(fileName) { + ['hive-site', 'webhcat-site', 'hive-env', 'core-site'].forEach(fileName => { if (configs[fileName]) { - Em.keys(configs[fileName]).forEach(function(propertyName) { - var currentValue = configs[fileName][propertyName], + Em.keys(configs[fileName]).forEach(propertyName => { + const currentValue = configs[fileName][propertyName], propertyDef = { - fileName: fileName, + fileName, name: propertyName, value: currentValue }, configProperty = initializer.initialValue(propertyDef, localDB, dependencies); initializer.updateSiteObj(configs[fileName], configProperty); if (this.get('isReconfigureRequired') && currentValue !== configs[fileName][propertyName]) { - var service = App.config.get('serviceByConfigTypeMap')[fileName]; + const service = App.config.get('serviceByConfigTypeMap')[fileName], + configObject = App.configsCollection.getConfigByName(propertyName, fileName), + displayName = configObject && configObject.displayName; propertiesToChange.pushObject({ propertyFileName: fileName, - propertyName: propertyName, + propertyName, + propertyTitle: configObject && Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, displayName === propertyName ? '' : propertyName), + propertyDescription: configObject && configObject.description, serviceDisplayName: service && service.get('displayName'), initialValue: currentValue, recommendedValue: propertyDef.value }); } - }, this); + }); } - }, this); + }); initializer.cleanup(); @@ -1416,7 +1434,7 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow * @method onLoadRangerConfigs */ onLoadRangerConfigs: function (data) { - var hdfsProperties = [ + const hdfsProperties = [ { type: 'core-site', name: 'hadoop.security.key.provider.path' @@ -1490,8 +1508,8 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow this.saveLoadedConfigs(data); - hdfsProperties.forEach(function (property) { - var typeConfigs = data.items.findProperty('type', property.type).properties, + hdfsProperties.forEach(property => { + const typeConfigs = data.items.findProperty('type', property.type).properties, currentValue = typeConfigs[property.name], pattern = new RegExp('^kms:\\/\\/http@(.+):' + rkmsPort + '\\/kms$'), patternMatch = currentValue && currentValue.match(pattern), @@ -1499,10 +1517,16 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow if (currentHostsList !== rkmsHostsStr) { typeConfigs[property.name] = newValue; if (this.get('isReconfigureRequired')) { - var service = App.config.get('serviceByConfigTypeMap')[property.type]; + const propertyFileName = property.type, + propertyName = property.name, + service = App.config.get('serviceByConfigTypeMap')[propertyFileName], + configObject = App.configsCollection.getConfigByName(propertyName, propertyFileName), + displayName = configObject && configObject.displayName; propertiesToChange.pushObject({ - propertyFileName: property.type, - propertyName: property.name, + propertyFileName, + propertyName, + propertyTitle: configObject && Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, displayName === propertyName ? '' : propertyName), + propertyDescription: configObject && configObject.description, serviceDisplayName: service && service.get('displayName'), initialValue: currentValue, recommendedValue: newValue, @@ -1510,7 +1534,7 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow }); } } - }, this); + }); kmsSiteProperties.forEach(function (property) { var currentValue = kmsSiteConfigs.properties[property.name]; diff --git a/ambari-web/app/controllers/main/service/item.js b/ambari-web/app/controllers/main/service/item.js index 1bc8410..6560666 100644 --- a/ambari-web/app/controllers/main/service/item.js +++ b/ambari-web/app/controllers/main/service/item.js @@ -1521,8 +1521,8 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow primary: popupPrimary, primaryClass: 'btn-danger', disablePrimary: Em.computed.alias('controller.isRecommendationInProgress'), - classNameBindings: ['controller.changedProperties.length:common-modal-wrapper', 'controller.changedProperties.length:modal-full-width'], - modalDialogClasses: Em.computed.ifThenElse('controller.changedProperties.length', ['modal-lg'], []), + classNameBindings: ['controller.changedProperties.length:common-modal-wrapper'], + modalDialogClasses: Em.computed.ifThenElse('controller.changedProperties.length', ['modal-xlg'], []), bodyClass: Em.View.extend({ templateName: require('templates/main/service/info/delete_service_warning_popup'), warningMessage: new Em.Handlebars.SafeString(warningMessage) diff --git a/ambari-web/app/mixins/common/configs/config_recommendations.js b/ambari-web/app/mixins/common/configs/config_recommendations.js index bccf3b7..6e51523 100644 --- a/ambari-web/app/mixins/common/configs/config_recommendations.js +++ b/ambari-web/app/mixins/common/configs/config_recommendations.js @@ -96,20 +96,24 @@ App.ConfigRecommendations = Em.Mixin.create({ */ addRecommendation: function (name, fileName, configGroupName, recommendedValue, initialValue, parentPropertyIds, isEditable) { Em.assert('name and fileName should be defined', name && fileName); - var site = App.config.getConfigTagFromFileName(fileName); - var service = App.config.get('serviceByConfigTypeMap')[site]; + const site = App.config.getConfigTagFromFileName(fileName); + const service = App.config.get('serviceByConfigTypeMap')[site]; + const configObject = App.configsCollection.getConfigByName(name, fileName); + const displayName = configObject && configObject.displayName; - var recommendation = { + const recommendation = { saveRecommended: true, saveRecommendedDefault: true, propertyFileName: site, propertyName: name, + propertyTitle: configObject && Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, displayName === name ? '' : name), + propertyDescription: configObject && configObject.description, isDeleted: Em.isNone(recommendedValue), notDefined: Em.isNone(initialValue), configGroup: configGroupName || "Default", - initialValue: initialValue, + initialValue, parentConfigs: parentPropertyIds || [], serviceName: service.get('serviceName'), allowChangeGroup: false,//TODO groupName!= "Default" && (service.get('serviceName') != this.get('selectedService.serviceName')) diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less index 367233b..3f4d2d2 100644 --- a/ambari-web/app/styles/application.less +++ b/ambari-web/app/styles/application.less @@ -20,7 +20,7 @@ body { overflow-y: scroll; - line-height: 1.3em; + line-height: @default-line-height; } html, body { @@ -2171,6 +2171,26 @@ input[type="radio"].align-checkbox, input[type="checkbox"].align-checkbox { .config-validation-warnings { table { + table-layout: fixed; + td, th { + &.issue-type-cell { + width: 5%; + } + &.service-name-cell { + width: 15%; + } + &.property-name-cell { + width: 15%; + } + &.property-value-cell { + width: 25%; + overflow: hidden; + overflow-wrap: break-word; + } + &.property-description-cell { + width: 40%; + } + } tbody{ tr { &.warning { @@ -2221,13 +2241,18 @@ input[type="radio"].align-checkbox, input[type="checkbox"].align-checkbox { #config-dependencies { max-height: 500px; overflow-y: visible; - td { - min-width: 120px; - word-break: break-all; + td, th { &.check-box-col { min-width: 5px; width: 5px; + .checkbox { + display: inline-block; + } } + } + td { + min-width: 120px; + word-break: break-all; &.config-dependency-name { min-width: @config-dependency-t-name-width; } diff --git a/ambari-web/app/styles/bootstrap_overrides.less b/ambari-web/app/styles/bootstrap_overrides.less index 9243ef6..70d9945 100644 --- a/ambari-web/app/styles/bootstrap_overrides.less +++ b/ambari-web/app/styles/bootstrap_overrides.less @@ -432,7 +432,7 @@ select.form-control { .btn-group.open .dropdown-menu input[type="checkbox"] + label, .dropdown.open .dropdown-menu input[type="checkbox"] + label { - line-height: 1.3em; + line-height: @default-line-height; } .navigation-bar-container.collapsed ul.nav.side-nav-menu li ul.sub-menu, diff --git a/ambari-web/app/styles/common.less b/ambari-web/app/styles/common.less index 15babfa..22ef496 100644 --- a/ambari-web/app/styles/common.less +++ b/ambari-web/app/styles/common.less @@ -189,6 +189,7 @@ @default-font-size: 14px; @smaller-font-size: 12px; @default-button-height: 34px; +@default-line-height: 1.3em; /************************************************************************ * Modal popup properties @@ -199,6 +200,10 @@ @modal-header-height: 57px; // modal footer height @modal-footer-height: 75px; +// default modal top margin +@modal-dialog-margin-top-default: 10px; +// modal top margin for wide screen +@modal-dialog-margin-top-wide-screen: 30px; .ellipsis-overflow { overflow: hidden; @@ -211,3 +216,11 @@ } @dashboard-widget-height: 157px; + +/************************************************************************ +* Top navbar styles +***********************************************************************/ +@navbar-header-vertical-padding: 19px; +@navbar-header-padding-right: 15px; +@navbar-header-padding-left: 0; +@navbar-header-font-size: 20px; diff --git a/ambari-web/app/styles/modal_popups.less b/ambari-web/app/styles/modal_popups.less index 0674541..52c036c 100644 --- a/ambari-web/app/styles/modal_popups.less +++ b/ambari-web/app/styles/modal_popups.less @@ -17,6 +17,8 @@ */ @import 'common.less'; +@modal-top-calculated-without-margin: @navbar-header-vertical-padding + (@navbar-header-font-size * unit(@default-line-height)); + #modal { outline: none; } @@ -25,7 +27,7 @@ margin-left: 20px; margin-right: 20px; text-indent: -20px; - line-height: 1.3em; + line-height: @default-line-height; a:not(.disabled) { cursor: pointer; @@ -97,7 +99,7 @@ } .modal { - top: 5%; + top: @modal-top-calculated-without-margin - @modal-dialog-margin-top-default; .modal-body { .top-wrap { &.top-wrap-header { @@ -107,6 +109,13 @@ } } } + +@media (min-width: 768px) { + .modal { + top: @modal-top-calculated-without-margin - @modal-dialog-margin-top-wide-screen; + } +} + .modal-body { max-height: 600px; overflow-y: auto; diff --git a/ambari-web/app/styles/top-nav.less b/ambari-web/app/styles/top-nav.less index 9fcc5f3..f14a014 100644 --- a/ambari-web/app/styles/top-nav.less +++ b/ambari-web/app/styles/top-nav.less @@ -26,9 +26,9 @@ margin-bottom: 10px; .navbar-header { - padding: 19px 15px 19px 0; + padding: @navbar-header-vertical-padding @navbar-header-padding-right @navbar-header-vertical-padding @navbar-header-padding-left; margin-top: -5px; - font-size: 20px; + font-size: @navbar-header-font-size; width: ~"calc(100% - 400px)"; a { color: #313D54; diff --git a/ambari-web/app/templates/common/configs/services_config.hbs b/ambari-web/app/templates/common/configs/services_config.hbs index adea9df..044f32e7 100644 --- a/ambari-web/app/templates/common/configs/services_config.hbs +++ b/ambari-web/app/templates/common/configs/services_config.hbs @@ -72,7 +72,9 @@ <div id="notifications-dropdown" class="dropdown-menu row"> <div class="popover-content"> <div class="popup-arrow-up"></div> - {{view App.DependentConfigsListView recommendationsBinding="controller.recommendations" requiredChangesBinding="controller.requiredChanges"}} + {{view App.DependentConfigsListView recommendationsBinding="controller.recommendations" + requiredChangesBinding="controller.requiredChanges" isRecommendationsClickable=true + showRecommendationsPopovers=false}} </div> </div> </div> diff --git a/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs b/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs index 9dbd401..0f51473 100644 --- a/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs +++ b/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs @@ -25,29 +25,29 @@ <table class="table table-hover"> <thead> <tr> - <th>{{t common.type}}</th> - <th>{{t common.service}}</th> - <th>{{t common.property}}</th> - <th>{{t common.value}}</th> - <th>{{t common.description}}</th> + <th class="issue-type-cell">{{t common.type}}</th> + <th class="service-name-cell">{{t common.service}}</th> + <th class="property-name-cell">{{t common.property}}</th> + <th class="property-value-cell">{{t common.value}}</th> + <th class="property-description-cell">{{t common.description}}</th> </tr> </thead> <tbody> {{#each error in view.configErrors.criticalIssues}} <tr {{bindAttr class="error.cssClass"}}> - <td> + <td class="issue-type-cell"> {{t common.critical}} </td> - <td>{{error.serviceName}}</td> - <td> + <td class="service-name-cell">{{error.serviceName}}</td> + <td class="property-name-cell"> {{#if controller.isInstallWizard}} <a href="#" {{action "showConfigProperty" error target="controller"}}>{{error.propertyName}}</a> {{else}} {{error.propertyName}} {{/if}} </td> - <td>{{error.value}}</td> - <td> + <td class="property-value-cell">{{error.value}}</td> + <td class="property-description-cell"> {{#each message in error.messages}} <div class="property-message">{{message}}</div> {{/each}} @@ -71,17 +71,17 @@ <table class="table table-hover"> <thead> <tr> - <th>{{t common.type}}</th> - <th>{{t common.service}}</th> - <th>{{t common.property}}</th> - <th>{{t installer.step7.popup.currentValue}}</th> - <th>{{t common.description}}</th> + <th class="issue-type-cell">{{t common.type}}</th> + <th class="service-name-cell">{{t common.service}}</th> + <th class="property-name-cell">{{t common.property}}</th> + <th class="property-value-cell">{{t installer.step7.popup.currentValue}}</th> + <th class="property-description-cell">{{t common.description}}</th> </tr> </thead> <tbody> {{#each error in view.configErrors.issues}} <tr {{bindAttr class="error.cssClass"}}> - <td> + <td class="issue-type-cell"> {{#if error.isError}} {{t common.error}} {{else}} @@ -94,16 +94,16 @@ <td colspan="4">{{error.message}}</td> {{/each}} {{else}} - <td>{{error.serviceName}}</td> - <td> + <td class="service-name-cell">{{error.serviceName}}</td> + <td class="property-name-cell"> {{#if controller.isInstallWizard}} <a href="#" {{action "showConfigProperty" error target="controller"}}>{{error.propertyName}}</a> {{else}} {{error.propertyName}} {{/if}} </td> - <td>{{error.value}}</td> - <td> + <td class="property-value-cell">{{error.value}}</td> + <td class="property-description-cell"> {{#each message in error.messages}} <div class="property-message">{{message}}</div> {{/each}} diff --git a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs index d5d54ff..d12af61 100644 --- a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs +++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs @@ -63,9 +63,11 @@ {{/if}} <span id="config-dependencies" class="limited-height-2"> {{#if view.recommendations.length}} - {{view App.DependentConfigsTableView recommendationsBinding="view.recommendations" checkboxesFirstBinding="controller.isInstallWizard" isClickable=true}} + {{view App.DependentConfigsTableView recommendationsBinding="view.recommendations" + isClickableBinding="view.isRecommendationsClickable" showPopoversBinding="view.showRecommendationsPopovers"}} {{/if}} {{#if view.requiredChanges.length}} - {{view App.DependentConfigsTableView recommendationsBinding="view.requiredChanges" isEditable=false checkboxesFirstBinding="controller.isInstallWizard" isClickable=true}} + {{view App.DependentConfigsTableView recommendationsBinding="view.requiredChanges" isEditable=false + isClickableBinding="view.isRecommendationsClickable" showPopoversBinding="view.showRecommendationsPopovers"}} {{/if}} </span> diff --git a/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs b/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs index 637ec26..1f506d0 100644 --- a/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs +++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs @@ -22,10 +22,8 @@ <table class="table table-hover"> <thead> <tr> - {{#if view.checkboxesFirst}} - {{#if view.isEditable}} - <th class="check-box-col">{{view view.parentView.toggleAll}}<label {{bindAttr for="view.parentView.toggleAllId"}}></label></th> - {{/if}} + {{#if view.isEditable}} + <th class="check-box-col">{{view view.parentView.toggleAll}}<label {{bindAttr for="view.parentView.toggleAllId"}}></label></th> {{/if}} <th>{{t common.property}}</th> <th>{{t common.service}}</th> @@ -45,22 +43,15 @@ </div> </div> </th> - {{#unless view.checkboxesFirst}} - {{#if view.isEditable}} - <th class="check-box-col">{{view view.parentView.toggleAll}}<label {{bindAttr for="view.parentView.toggleAllId"}}></label></th> - {{/if}} - {{/unless}} </tr> </thead> <tbody> {{#each recommendation in view.recommendations}} <tr {{bindAttr class="recommendation.saveRecommended:active"}}> - {{#if view.checkboxesFirst}} - {{#if view.isEditable}} - <td class="check-box-col">{{view App.CheckboxView checkedBinding="recommendation.saveRecommended"}}</td> - {{/if}} + {{#if view.isEditable}} + <td class="check-box-col">{{view App.CheckboxView checkedBinding="recommendation.saveRecommended"}}</td> {{/if}} - <td class="config-dependency-name"> + <td class="config-dependency-name" {{bindAttr data-original-title="recommendation.propertyTitle" data-content="recommendation.propertyDescription"}}> {{#if view.isClickable}} <a href="#" {{action "showConfigProperty" recommendation target="controller"}}>{{recommendation.propertyName}}</a> {{else}} @@ -82,11 +73,6 @@ {{view App.ConfigDiffView configBinding="recommendation"}} </div> </td> - {{#unless view.checkboxesFirst}} - {{#if view.isEditable}} - <td class="check-box-col">{{view App.CheckboxView checkedBinding="recommendation.saveRecommended"}}</td> - {{/if}} - {{/unless}} </tr> {{/each}} </tbody> diff --git a/ambari-web/app/views/common/modal_popup.js b/ambari-web/app/views/common/modal_popup.js index ad27a40..4b9830f 100644 --- a/ambari-web/app/views/common/modal_popup.js +++ b/ambari-web/app/views/common/modal_popup.js @@ -162,12 +162,19 @@ App.ModalPopup = Ember.View.extend({ fitHeight: function () { if (this.get('state') === 'destroyed') return; - var popup = this.$().find('#modal'), + const popup = this.$().find('#modal'), wrapper = $(popup).find('.modal-dialog'), block = $(popup).find('.modal-body'), wh = $(window).height(), - top = wh * 0.05, - newMaxHeight = wh - top * 2 - (wrapper.height() - block.height()); + ww = $(window).width(), + topNavPaddingTop = 19, // from ambari-web/app/styles/common.less + topNavFontSize = 20, // from ambari-web/app/styles/common.less + topNavLineHeight = 1.3, // from ambari-web/app/styles/common.less + modalMarginTopDefault = 10, // from ambari-web/app/styles/common.less + modalMarginTopWide = 30, // from ambari-web/app/styles/common.less + modalMarginTop = ww < 768 ? modalMarginTopDefault : modalMarginTopWide, // from ambari-web/vendor/styles/bootstrap.css + top = topNavPaddingTop + topNavFontSize * topNavLineHeight - modalMarginTop; + let newMaxHeight = wh - top * 2 - (wrapper.height() - block.height()); popup.css({ 'top': top + 'px', diff --git a/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js b/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js index 665226d..2271be1 100644 --- a/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js +++ b/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js @@ -26,8 +26,8 @@ var App = require('app'); App.showConfigValidationPopup = function (configErrors, primary, secondary, controller) { return App.ModalPopup.show({ header: Em.I18n.t('installer.step7.popup.validation.warning.header'), - classNames: ['common-modal-wrapper','modal-full-width'], - modalDialogClasses: ['modal-lg'], + classNames: ['common-modal-wrapper'], + modalDialogClasses: ['modal-xlg'], primary: Em.I18n.t('common.proceedAnyway'), primaryClass: 'btn-danger', marginBottom: 200, diff --git a/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js b/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js index 7f0c581..451d038 100644 --- a/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js +++ b/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js @@ -21,8 +21,11 @@ var App = require('app'); App.DependentConfigsTableView = Em.View.extend({ templateName: require('templates/common/modal_popups/dependent_configs_table'), recommendations: [], - checkboxesFirst: false, isClickable: false, + showPopovers: true, + elementsWithPopover: function () { + return this.$('td.config-dependency-name'); + }.property(), hideMessage: function () { return this.get('controller.isInstallWizard'); }.property('controller.isInstallWizard'), @@ -47,12 +50,28 @@ App.DependentConfigsTableView = Em.View.extend({ message += Em.I18n.t('popup.dependent.configs.title.required'); } return message; - }.property('isEditable') + }.property('isEditable'), + didInsertElement: function () { + if (this.get('showPopovers')) { + App.popover(this.get('elementsWithPopover'), { + placement: 'auto right', + trigger: 'hover', + html: true + }); + } + }, + willDestroyElement: function () { + if (this.get('showPopovers')) { + this.get('elementsWithPopover').popover('destroy'); + } + } }); App.DependentConfigsListView = Em.View.extend({ templateName: require('templates/common/modal_popups/dependent_configs_list'), isAfterRecommendation: true, + isRecommendationsClickable: false, + showRecommendationsPopovers: true, recommendations: [], requiredChanges: [], allConfigsWithErrors: [], @@ -96,32 +115,36 @@ App.DependentConfigsListView = Em.View.extend({ /** * Show confirmation popup - * @param {[Object]} recommendedChanges + * @param {[Object]} recommendations * @param {[Object]} requiredChanges * @param {function} [primary=null] * @param {function} [secondary=null] + * @param {boolean} [isRecommendationsClickable=false] + * @param {boolean} [isRecommendationsClickable=true] * we use this parameter to defer saving configs before we make some decisions. * @return {App.ModalPopup} */ -App.showDependentConfigsPopup = function (recommendedChanges, requiredChanges, primary, secondary, controller) { +App.showDependentConfigsPopup = function (recommendations, requiredChanges, primary, secondary, controller, isRecommendationsClickable = false, showRecommendationsPopovers = true) { return App.ModalPopup.show({ encodeBody: false, header: Em.I18n.t('popup.dependent.configs.header'), - classNames: ['common-modal-wrapper','modal-full-width'], - modalDialogClasses: ['modal-lg'], + classNames: ['common-modal-wrapper'], + modalDialogClasses: ['modal-xlg'], secondaryClass: 'cancel-button', bodyClass: App.DependentConfigsListView.extend({ - recommendations: recommendedChanges, - requiredChanges: requiredChanges, - controller: controller + recommendations, + requiredChanges, + controller, + isRecommendationsClickable, + showRecommendationsPopovers }), saveChanges: function() { - recommendedChanges.forEach(function (c) { + recommendations.forEach(function (c) { Em.set(c, 'saveRecommendedDefault', Em.get(c, 'saveRecommended')); }); }, discardChanges: function() { - recommendedChanges.forEach(function(c) { + recommendations.forEach(function(c) { Em.set(c, 'saveRecommended', Em.get(c, 'saveRecommendedDefault')); }); }, diff --git a/ambari-web/app/views/common/modal_popups/log_file_search_popup.js b/ambari-web/app/views/common/modal_popups/log_file_search_popup.js index af40367..3c5ca11 100644 --- a/ambari-web/app/views/common/modal_popups/log_file_search_popup.js +++ b/ambari-web/app/views/common/modal_popups/log_file_search_popup.js @@ -20,7 +20,7 @@ var App = require('app'); App.LogFileSearchPopup = function(header) { return App.ModalPopup.show({ - classNames: ['modal-full-width', 'common-modal-wrapper', 'log-file-search-popup'], + classNames: ['common-modal-wrapper', 'log-file-search-popup'], modalDialogClasses: ['modal-lg'], header: header, bodyView: null, diff --git a/ambari-web/test/mixins/common/configs/config_recommendations_test.js b/ambari-web/test/mixins/common/configs/config_recommendations_test.js index a72f6d3..42fc7aa 100644 --- a/ambari-web/test/mixins/common/configs/config_recommendations_test.js +++ b/ambari-web/test/mixins/common/configs/config_recommendations_test.js @@ -32,12 +32,17 @@ describe('App.ConfigRecommendations', function() { sinon.stub(App.config, 'get').withArgs('serviceByConfigTypeMap').returns({ 'pFile': Em.Object.create({serviceName: 'sName', displayName: 'sDisplayName'}) }); - sinon.stub(Handlebars, 'SafeString'); + sinon.stub(App.configsCollection, 'getConfigByName').withArgs('pName', 'pFile').returns({ + displayName: 'pDisplayName', + description: 'pDescription' + }); + sinon.stub(Handlebars, 'SafeString'); }); afterEach(function() { instanceObject.formatParentProperties.restore(); App.config.get.restore(); - Handlebars.SafeString.restore(); + App.configsCollection.getConfigByName.restore(); + Handlebars.SafeString.restore(); }); it('adds new recommendation', function() { @@ -47,6 +52,8 @@ describe('App.ConfigRecommendations', function() { saveRecommendedDefault: true, propertyFileName: 'pFile', propertyName: 'pName', + propertyTitle: 'pDisplayName<br><small>pName</small>', + propertyDescription: 'pDescription', isDeleted: false, notDefined: false, configGroup: 'pGroup', @@ -123,6 +130,8 @@ describe('App.ConfigRecommendations', function() { saveRecommendedDefault: true, propertyFileName: 'pFile', propertyName: 'pName', + propertyTitle: 'pDisplayName<br><small>pName</small>', + propertyDescription: 'pDescription', isDeleted: false, notDefined: false, configGroup: 'pGroup', @@ -144,6 +153,8 @@ describe('App.ConfigRecommendations', function() { saveRecommendedDefault: true, propertyFileName: 'pFile', propertyName: 'pName', + propertyTitle: 'pDisplayName<br><small>pName</small>', + propertyDescription: 'pDescription', isDeleted: false, notDefined: false, configGroup: 'pGroup', @@ -165,6 +176,8 @@ describe('App.ConfigRecommendations', function() { saveRecommendedDefault: true, propertyFileName: 'pFile', propertyName: 'pName', + propertyTitle: 'pDisplayName<br><small>pName</small>', + propertyDescription: 'pDescription', isDeleted: true, notDefined: true, configGroup: 'Default', @@ -187,12 +200,17 @@ describe('App.ConfigRecommendations', function() { 'pFile': c.service }); sinon.stub(Handlebars, 'SafeString'); + sinon.stub(App.configsCollection, 'getConfigByName').withArgs('pName', 'pFile.xml').returns({ + displayName: 'pDisplayName', + description: 'pDescription' + }); recommendation = instanceObject.addRecommendation(c.name, c.file, c.group, c.recommended, c.initial, c.parent, c.isEditable); }); afterEach(function() { App.config.get.restore(); Handlebars.SafeString.restore(); + App.configsCollection.getConfigByName.restore(); }); it(c.title, function() {