Explorar o código

AMBARI-5945. Provide way to add 'custom' parameters via single or bulk.(Levgen F via xiwang)

Xi Wang %!s(int64=11) %!d(string=hai) anos
pai
achega
e40b18a595

+ 11 - 1
ambari-web/app/messages.js

@@ -104,6 +104,7 @@ Em.I18n.translations = {
   'common.metrics':'Metrics',
   'common.timeRange':'Time Range',
   'common.name':'Name',
+  'common.key':'Key',
   'common.value':'Value',
   'common.ipAddress':'IP Address',
   'common.cpu':'CPU',
@@ -782,6 +783,7 @@ Em.I18n.translations = {
 
   'form.validator.invalidIp':'Please enter valid ip address',
   'form.validator.configKey':'Invalid Key. Only alphanumerics, hyphens, underscores, asterisks and periods are allowed.',
+  'form.validator.configKey.specific':'"{0}" is invalid Key. Only alphanumerics, hyphens, underscores, asterisks and periods are allowed.',
 
   'admin.advanced.caution':'This section is for advanced user only.<br/>Proceed with caution.',
   'admin.advanced.button.uninstallIncludingData':'Uninstall cluster including all data.',
@@ -1315,9 +1317,17 @@ Em.I18n.translations = {
   'services.service.config.failCreateConfig' : 'Failure in creating service configuration',
   'services.service.config.failSaveConfig':'Failure in saving service configuration',
   'services.service.config.failSaveConfigHostOverrides':'Failure in saving service configuration overrides',
-  'services.service.config.addPropertyWindow.errorMessage':'This is required',
+  'services.service.config.addPropertyWindow.error.required':'This is required',
   'services.service.config.addPropertyWindow.error.derivedKey':'This property is already defined',
+  'services.service.config.addPropertyWindow.error.derivedKey.specific':'Property "{0}" is already defined',
+  'services.service.config.addPropertyWindow.error.format':'Key and value should be separated by "=" sign',
+  'services.service.config.addPropertyWindow.error.lineNumber':'Line {0}: ',
   'services.service.config.addPropertyWindow.filterKeyLink' : 'Find property',
+  'services.service.config.addPropertyWindow.properties' : 'Properties',
+  'services.service.config.addPropertyWindow.propertiesHelper' : 'key=value (one per line)',
+  'services.service.config.addPropertyWindow.propertiesPlaceholder' : 'Enter key=value (one per line)',
+  'services.service.config.addPropertyWindow.bulkMode' : 'Bulk property add mode',
+  'services.service.config.addPropertyWindow.singleMode' : 'Single property add mode',
   'services.service.config.stopService.runningHostComponents':'{0} components on {1} hosts are running',
   'services.service.config.stopService.unknownHostComponents':'{0} components on {1} hosts are in unknown state.  These components might actually be running and need restarting later to make the changes effective.',
   'services.service.config.confirmDirectoryChange':'You are about to make changes to service directories that are core to {0}. Before you proceed, be absolutely certain of the implications and that you have taken necessary manual steps, if any, for the changes. Are you sure you want to proceed?',

+ 9 - 0
ambari-web/app/styles/application.less

@@ -6585,6 +6585,15 @@ i.icon-asterisks {
   }
 }
 
+.add-property-window {
+  .control-label {
+    text-align: left;
+  }
+  .controls {
+    margin-left: 140px;
+  }
+}
+
 #rm-ha-wizard {
   .rm-host-select {
     width: 95%;

+ 42 - 12
ambari-web/app/templates/common/configs/addPropertyWindow.hbs

@@ -15,29 +15,59 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 }}
-  <form class="form-horizontal" autocomplete="off">
-    <div class="each-row">
-      <label class="control-label">Type</label>
+  <form class="form-horizontal add-property-window" autocomplete="off">
+    <div class="each-row control-group">
+      <label class="control-label">{{t common.type}}</label>
       <div class="controls">
-        {{view.serviceConfigProperty.filename}}
+        <input class="span4" type="text" disabled {{bindAttr value="view.fileName"}}/>
+        <div class="btn-group add-mode pull-right" {{action toggleBulkMode target="view" }}>
+          <a href="#"
+            {{bindAttr class=":btn view.serviceConfigObj.isBulkMode::active"}}
+            {{translateAttr title="services.service.config.addPropertyWindow.singleMode" }}
+            data-toggle="tooltip"><i class="icon-tag"></i></a>
+          <a href="#"
+            {{bindAttr class=":btn view.serviceConfigObj.isBulkMode:active"}}
+            {{translateAttr data-original-title="services.service.config.addPropertyWindow.bulkMode" }}
+            data-toggle="tooltip"><i class="icon-tags"></i></a>
+        </div>
       </div>
     </div>
-    <div {{bindAttr class="view.serviceConfigProperty.isKeyError:error :each-row :control-group"}}>
-      <label class="control-label">Key</label>
+    {{#if view.serviceConfigObj.isBulkMode}}
+    <div {{bindAttr class="view.serviceConfigObj.bulkConfigError:error :each-row :control-group"}}>
+      <label class="control-label">
+        {{t services.service.config.addPropertyWindow.properties}}
+        <br>
+        <small>{{t services.service.config.addPropertyWindow.propertiesHelper}}</small>
+      </label>
       <div class="controls">
-        {{view Ember.TextField valueBinding="view.serviceConfigProperty.name" class="span4"}}
-        <span class="help-inline">{{view.serviceConfigProperty.errorMessage}}</span>
-        {{#if view.serviceConfigProperty.showFilterLink}}
+        {{view Ember.TextArea
+          valueBinding="view.serviceConfigObj.bulkConfigValue" rows="4"
+          classNames="input-block-level"
+          placeholderTranslation="services.service.config.addPropertyWindow.propertiesPlaceholder"
+        }}
+        <span class="help-inline">{{{view.serviceConfigObj.bulkConfigErrorMessage}}}</span>
+      </div>
+    </div>
+    {{else}}
+    <div {{bindAttr class="view.serviceConfigObj.isKeyError:error :each-row :control-group"}}>
+      <label class="control-label">{{t common.key}}</label>
+      <div class="controls">
+        {{view Ember.TextField valueBinding="view.serviceConfigObj.name" class="input-block-level"}}
+        {{#if view.serviceConfigObj.isKeyError}}
+          <span class="help-inline">{{view.serviceConfigObj.errorMessage}}</span>
+        {{/if}}
+        {{#if view.serviceConfigObj.showFilterLink}}
           <a href="#" class="btn-padding" {{action filterByKey target="view" }}>
             {{t services.service.config.addPropertyWindow.filterKeyLink}}
           </a>
         {{/if}}
       </div>
     </div>
-    <div class="each-row">
-      <label class="control-label">Value</label>
+    <div class="each-row control-group">
+      <label class="control-label">{{t common.value}}</label>
       <div class="controls">
-        {{view Ember.TextArea valueBinding="view.serviceConfigProperty.value" rows="4" classNames="span6" }}
+        {{view Ember.TextArea valueBinding="view.serviceConfigObj.value" rows="4" classNames="input-block-level"}}
       </div>
     </div>
+    {{/if}}
   </form>

+ 2 - 0
ambari-web/app/utils/helper.js

@@ -223,6 +223,8 @@ Em.CoreObject.reopen({
   }
 });
 
+Em.TextArea.reopen(Em.I18n.TranslateableAttributes);
+
 /** @namespace Em.Handlebars **/
 Em.Handlebars.registerHelper('log', function (variable) {
   console.log(variable);

+ 183 - 84
ambari-web/app/views/common/configs/services_config.js

@@ -89,7 +89,7 @@ App.ServiceConfigView = Em.View.extend({
 });
 
 
-App.ServiceConfigsByCategoryView = Ember.View.extend({
+App.ServiceConfigsByCategoryView = Ember.View.extend(App.UserPref, {
 
   classNames: ['accordion-group', 'common-config-category'],
   classNameBindings: ['category.name', 'isShowBlock::hidden'],
@@ -459,98 +459,197 @@ App.ServiceConfigsByCategoryView = Ember.View.extend({
     var category = this.get('category');
     return category.indexOf("Advanced") != -1;
   },
-  showAddPropertyWindow: function (event) {
-    var configsOfFile = this.get('service.configs').filterProperty('filename', this.get('category.siteFileName'));
-    var self =this;
-    var serviceConfigObj = Ember.Object.create({
-      name: '',
-      value: '',
-      defaultValue: null,
-      filename: '',
-      isUserProperty: true,
-      isKeyError: false,
-      showFilterLink: false,
-      isNotSaved: true,
-      errorMessage: "",
-      observeAddPropertyValue: function () {
-        var name = this.get('name');
-        if (name.trim() != "") {
-          if (validator.isValidConfigKey(name)) {
-            var configMappingProperty = App.config.get('configMapping').all().filterProperty('filename',self.get('category.siteFileName')).findProperty('name', name);
-            if ((configMappingProperty == null) && (!configsOfFile.findProperty('name', name))) {
-              this.set("isKeyError", false);
-              this.set("errorMessage", "");
+
+  persistKey: function () {
+    return 'admin-bulk-add-properties-' + App.router.get('loginName');
+  },
+
+  showAddPropertyWindow: function () {
+    var persistController = this;
+    var modePersistKey = this.persistKey();
+
+    persistController.getUserPref(modePersistKey).pipe(function (data) {
+      return !!data;
+    }, function () {
+      return false;
+    }).always((function (isBulkMode) {
+
+      var category = this.get('category');
+      var siteFileName = category.get('siteFileName');
+
+      var service = this.get('service');
+      var serviceName = service.get('serviceName');
+
+      var secureConfigs = this.get('controller.secureConfigs').filterProperty('filename', siteFileName);
+
+      function isSecureConfig(configName) {
+        return !!secureConfigs.findProperty('name', configName);
+      }
+
+      var configsOfFile = service.get('configs').filterProperty('filename', siteFileName);
+      var siteFileProperties = App.config.get('configMapping').all().filterProperty('filename', siteFileName);
+
+      function isDuplicatedConfigKey(name) {
+        return siteFileProperties.findProperty('name', name) || configsOfFile.findProperty('name', name);
+      }
+
+      var serviceConfigs = this.get('serviceConfigs');
+
+      function createProperty(propertyName, propertyValue) {
+        serviceConfigs.pushObject(App.ServiceConfigProperty.create({
+          name: propertyName,
+          displayName: propertyName,
+          value: propertyValue,
+          displayType: stringUtils.isSingleLine(propertyValue) ? 'advanced' : 'multiLine',
+          isSecureConfig: isSecureConfig(propertyName),
+          category: category.get('name'),
+          id: 'site property',
+          serviceName: serviceName,
+          defaultValue: null,
+          filename: siteFileName || '',
+          isUserProperty: true,
+          isNotSaved: true
+        }));
+      }
+
+      var serviceConfigObj = Ember.Object.create({
+        isBulkMode: isBulkMode,
+        bulkConfigValue: '',
+        bulkConfigError: false,
+        bulkConfigErrorMessage: '',
+
+        name: '',
+        value: '',
+        isKeyError: false,
+        showFilterLink: false,
+        errorMessage: '',
+        observeAddPropertyValue: function () {
+          var name = this.get('name');
+          if (name.trim() != '') {
+            if (validator.isValidConfigKey(name)) {
+              if (!isDuplicatedConfigKey(name)) {
+                this.set('showFilterLink', false);
+                this.set('isKeyError', false);
+                this.set('errorMessage', '');
+              } else {
+                this.set('showFilterLink', true);
+                this.set('isKeyError', true);
+                this.set('errorMessage', Em.I18n.t('services.service.config.addPropertyWindow.error.derivedKey'));
+              }
             } else {
-              this.set("showFilterLink", true);
-              this.set("isKeyError", true);
-              this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.error.derivedKey'));
+              this.set('showFilterLink', false);
+              this.set('isKeyError', true);
+              this.set('errorMessage', Em.I18n.t('form.validator.configKey'));
             }
           } else {
-            this.set("isKeyError", true);
-            this.set("errorMessage", Em.I18n.t('form.validator.configKey'));
+            this.set('showFilterLink', false);
+            this.set('isKeyError', true);
+            this.set('errorMessage', Em.I18n.t('services.service.config.addPropertyWindow.error.required'));
           }
-        } else {
-          this.set("isKeyError", true);
-          this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.errorMessage'));
-        }
-      }.observes("name")
-    });
+        }.observes('name')
+      });
 
-    var category = this.get('category');
-    serviceConfigObj.displayType = "advanced";
-    serviceConfigObj.category = category.get('name');
-
-    var serviceName = this.get('service.serviceName');
-    var serviceConfigsMetaData = App.config.get('preDefinedServiceConfigs');
-    var serviceConfigMetaData = serviceConfigsMetaData.findProperty('serviceName', serviceName);
-    var categoryMetaData = serviceConfigMetaData == null ? null : serviceConfigMetaData.get('configCategories').findProperty('name', category.get('name'));
-    if (categoryMetaData != null) {
-      serviceConfigObj.filename = categoryMetaData.siteFileName;
-    }
+      function processConfig(config, callback) {
+        var lines = config.split('\n');
+        var errorMessages = [];
+        var parsedConfig = {};
+        var propertyCount = 0;
 
-    var self = this;
-    App.ModalPopup.show({
-      classNames: [ 'sixty-percent-width-modal'],
-      header: "Add Property",
-      primary: 'Add',
-      secondary: 'Cancel',
-      didInsertElement: function(){
-        this.$('input').focus();
-      },
-      onPrimary: function () {
-        serviceConfigObj.observeAddPropertyValue();
-        /**
-         * For the first entrance use this if (serviceConfigObj.name.trim() != "")
-         */
-        if (!serviceConfigObj.isKeyError) {
-          serviceConfigObj.displayName = serviceConfigObj.name;
-          serviceConfigObj.id = 'site property';
-          serviceConfigObj.serviceName = serviceName;
-          serviceConfigObj.displayType = stringUtils.isSingleLine(serviceConfigObj.get('value')) ? 'advanced' : 'multiLine';
-          var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj);
-          self.get('controller.secureConfigs').filterProperty('filename', self.get('category.siteFileName')).forEach(function (_secureConfig) {
-            if (_secureConfig.name === serviceConfigProperty.get('name')) {
-              serviceConfigProperty.set('isSecureConfig', true);
-            }
-          }, this);
-          self.get('serviceConfigs').pushObject(serviceConfigProperty);
-          this.hide();
+        function lineNumber(index) {
+          return Em.I18n.t('services.service.config.addPropertyWindow.error.lineNumber').format(index + 1);
         }
-      },
-      bodyClass: Ember.View.extend({
-        templateName: require('templates/common/configs/addPropertyWindow'),
-        controllerBinding: 'App.router.mainServiceInfoConfigsController',
-        serviceConfigProperty: serviceConfigObj,
-        filterByKey: function(event) {
-          var controller = (App.router.get('currentState.name') != 'configs')
-            ? App.router.get('wizardStep7Controller')
-            : App.router.get('mainServiceInfoConfigsController');
-          this.get('parentView').onClose();
-          controller.set('filter', event.view.get('serviceConfigProperty.name'));
+
+        lines.forEach(function (line, index) {
+          if (line.trim() === '') {
+            return;
+          }
+          var delimiter = '=';
+          var delimiterPosition = line.indexOf(delimiter);
+          if (delimiterPosition === -1) {
+            errorMessages.push(lineNumber(index) + Em.I18n.t('services.service.config.addPropertyWindow.error.format'));
+            return;
+          }
+          var key = Em.Handlebars.Utils.escapeExpression(line.slice(0, delimiterPosition).trim());
+          var value = line.slice(delimiterPosition + 1);
+          if (validator.isValidConfigKey(key)) {
+            if (!isDuplicatedConfigKey(key) && !(key in parsedConfig)) {
+              parsedConfig[key] = value;
+              propertyCount++;
+            } else {
+              errorMessages.push(lineNumber(index) + Em.I18n.t('services.service.config.addPropertyWindow.error.derivedKey.specific').format(key));
+            }
+          } else {
+            errorMessages.push(lineNumber(index) + Em.I18n.t('form.validator.configKey.specific').format(key));
+          }
+        });
+
+        if (errorMessages.length > 0) {
+          callback(errorMessages.join('<br>'), parsedConfig);
+        } else if (propertyCount === 0) {
+          callback(Em.I18n.t('services.service.config.addPropertyWindow.propertiesPlaceholder', parsedConfig));
+        } else {
+          callback(null, parsedConfig);
         }
-      })
-    });
+      }
+
+      App.ModalPopup.show({
+        classNames: ['sixty-percent-width-modal'],
+        header: 'Add Property',
+        primary: 'Add',
+        secondary: 'Cancel',
+        onPrimary: function () {
+          if (serviceConfigObj.isBulkMode) {
+            var popup = this;
+            processConfig(serviceConfigObj.bulkConfigValue, function (error, parsedConfig) {
+              if (error) {
+                serviceConfigObj.set('bulkConfigError', true);
+                serviceConfigObj.set('bulkConfigErrorMessage', error);
+              } else {
+                for (var key in parsedConfig) {
+                  if (parsedConfig.hasOwnProperty(key)) {
+                    createProperty(key, parsedConfig[key]);
+                  }
+                }
+                popup.hide();
+              }
+            });
+          } else {
+            serviceConfigObj.observeAddPropertyValue();
+            /**
+             * For the first entrance use this if (serviceConfigObj.name.trim() != '')
+             */
+            if (!serviceConfigObj.isKeyError) {
+              createProperty(serviceConfigObj.get('name'), serviceConfigObj.get('value'));
+              this.hide();
+            }
+          }
+        },
+        bodyClass: Ember.View.extend({
+          fileName: siteFileName,
+          templateName: require('templates/common/configs/addPropertyWindow'),
+          controllerBinding: 'App.router.mainServiceInfoConfigsController',
+          serviceConfigObj: serviceConfigObj,
+          didInsertElement: function() {
+            App.tooltip(this.$("[data-toggle=tooltip]"),{
+              placement: "top"
+            });
+          },
+          toggleBulkMode: function () {
+            var newMode = !this.serviceConfigObj.get('isBulkMode');
+            this.serviceConfigObj.set('isBulkMode', newMode);
+            persistController.postUserPref(modePersistKey, newMode);
+          },
+          filterByKey: function (event) {
+            var controller = (App.router.get('currentState.name') != 'configs')
+              ? App.router.get('wizardStep7Controller')
+              : App.router.get('mainServiceInfoConfigsController');
+            this.get('parentView').onClose();
+            controller.set('filter', event.view.get('serviceConfigObj.name'));
+          }
+        })
+      });
 
+    }).bind(this));
   },
 
   /**