瀏覽代碼

AMBARI-3767. Provide basic config-group management dialog. (akovalenko)

Aleksandr Kovalenko 11 年之前
父節點
當前提交
ef55a79c26

+ 1 - 0
ambari-web/app/controllers.js

@@ -72,6 +72,7 @@ require('controllers/main/service/reassign/step3_controller');
 require('controllers/main/service/reassign/step4_controller');
 require('controllers/main/service/reassign/step5_controller');
 require('controllers/main/service/reassign/step6_controller');
+require('controllers/main/service/manage_config_groups_controller');
 require('controllers/main/host');
 require('controllers/main/host/details');
 require('controllers/main/host/configs_service');

+ 19 - 0
ambari-web/app/controllers/main/service/item.js

@@ -215,6 +215,25 @@ App.MainServiceItemController = Em.Controller.extend({
     App.router.transitionTo('reassign');
   },
 
+  manageConfigurationGroups: function () {
+    var serviceName = this.get('content.serviceName');
+    var displayName = this.get('content.displayName');
+    App.ModalPopup.show({
+      header: Em.I18n.t('services.service.config_groups_popup.header').format(displayName),
+      bodyClass: App.MainServiceManageConfigGroupView.extend({
+        serviceName: serviceName,
+        controllerBinding: 'App.router.manageConfigGroupsController'
+      }),
+      classNames: ['sixty-percent-width-modal', 'manage-configuration-group-popup'],
+      primary: Em.I18n.t('common.save'),
+      onPrimary: function() {
+        this.hide();
+      },
+      secondary : Em.I18n.t('common.cancel'),
+      didInsertElement: function () {}
+    });
+  },
+
   /**
    * On click callback for <code>action</code> dropdown menu
    * Calls runSmokeTest, runRebalancer, runCompaction or reassignMaster depending on context

+ 144 - 0
ambari-web/app/controllers/main/service/manage_config_groups_controller.js

@@ -0,0 +1,144 @@
+/**
+ * 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');
+App.ManageConfigGroupsController = App.WizardController.extend({
+
+  name: 'manageConfigGroupsController',
+
+  isLoaded: false,
+
+  serviceName: null,
+
+  configGroups: [],
+
+  selectedConfigGroup: null,
+
+  loadConfigGroups: function (serviceName) {
+    this.set('serviceName', serviceName);
+    App.ajax.send({
+      name: 'service.load_config_groups',
+      sender: this,
+      data: {
+        serviceName: serviceName
+      },
+      success: 'onLoadConfigGroupsSuccess',
+      error: 'onLoadConfigGroupsError'
+    });
+  },
+
+  onLoadConfigGroupsSuccess: function (data) {
+    var usedHosts = [];
+    var unusedHosts = [];
+    var defaultConfigGroup = App.ConfigGroup.create({
+      name: "Default",
+      description: "Default cluster level " + this.get('serviceName') + " configuration",
+      isDefault: true,
+      parentConfigGroup: null,
+      service: this.get('content'),
+      configSiteTags: []
+    });
+    if (data && data.items) {
+      var groupToTypeToTagMap = {};
+      var configGroups = [];
+      data.items.forEach(function (configGroup) {
+        configGroup = configGroup.ConfigGroup;
+        var newConfigGroup = App.ConfigGroup.create({
+          id: configGroup.id,
+          name: configGroup.group_name,
+          description: configGroup.description,
+          isDefault: false,
+          parentConfigGroup: defaultConfigGroup,
+          service: App.Service.find().findProperty('serviceName', configGroup.tag),
+          hosts: configGroup.hosts.mapProperty('host_name'),
+          configSiteTags: [],
+          properties: []
+        });
+        usedHosts = usedHosts.concat(newConfigGroup.get('hosts'));
+        configGroups.push(newConfigGroup);
+        configGroup.desired_configs.forEach(function (config) {
+          if (!groupToTypeToTagMap[configGroup.group_name]) {
+            groupToTypeToTagMap[configGroup.group_name] = {}
+          }
+          groupToTypeToTagMap[configGroup.group_name][config.type] = config.tag;
+        });
+      }, this);
+      unusedHosts = App.Host.find().mapProperty('hostName');
+      usedHosts.uniq().forEach(function (host) {
+        unusedHosts = unusedHosts.without(host);
+      }, this);
+      defaultConfigGroup.set('childConfigGroups', configGroups);
+      defaultConfigGroup.set('hosts', unusedHosts);
+      this.set('configGroups', [defaultConfigGroup].concat(configGroups));
+      this.loadProperties(groupToTypeToTagMap);
+      this.set('isLoaded', true);
+    }
+  },
+
+  onLoadConfigGroupsError: function () {
+    console.error('Unable to load config groups for service.');
+  },
+
+  loadProperties: function (groupToTypeToTagMap) {
+    var typeTagToGroupMap = {};
+    var urlParams = [];
+    for (var group in groupToTypeToTagMap) {
+      var overrideTypeTags = groupToTypeToTagMap[group];
+      for (var type in overrideTypeTags) {
+        var tag = overrideTypeTags[type];
+        typeTagToGroupMap[type + "///" + tag] = group;
+        urlParams.push('(type=' + type + '&tag=' + tag + ')');
+      }
+    }
+    var params = urlParams.join('|');
+    if (urlParams.length) {
+      App.ajax.send({
+        name: 'config.host_overrides',
+        sender: this,
+        data: {
+          params: params,
+          typeTagToGroupMap: typeTagToGroupMap
+        },
+        success: 'onLoadPropertiesSuccess'
+      });
+    }
+  },
+
+  onLoadPropertiesSuccess: function (data, opt, params) {
+    data.items.forEach(function (configs) {
+      var typeTagConfigs = [];
+      App.config.loadedConfigurationsCache[configs.type + "_" + configs.tag] = configs.properties;
+      var group = params.typeTagToGroupMap[configs.type + "///" + configs.tag];
+      for (var config in configs.properties) {
+        typeTagConfigs.push({
+          name: config,
+          value: configs.properties[config]
+        });
+      }
+      this.get('configGroups').findProperty('name', group).get('properties').pushObjects(typeTagConfigs);
+    }, this);
+  },
+
+  showProperties: function () {
+    var properies = this.get('selectedConfigGroup.propertiesList');
+    if (properies) {
+      App.showAlertPopup(Em.I18n.t('services.service.config_groups_popup.properties'), properies);
+    }
+  }
+});

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

@@ -156,6 +156,8 @@ Em.I18n.translations = {
   'common.allServices':'All Services',
   'common.move':'Move',
   'common.change': 'Change',
+  'common.overrides': 'Overrides',
+  'common.properties': 'properties',
 
   'requestInfo.installComponents':'Install Components',
   'requestInfo.installServices':'Install Services',
@@ -953,6 +955,7 @@ Em.I18n.translations = {
   'services.service.actions.run.smoke':'Run Smoke Test',
   'services.service.actions.reassign.master':'Reassign {0}',
   'services.service.actions.reassign.master.hive':'Reassign HiveServer2, WebHCat Server, MySQL Server',
+  'services.service.actions.manage_configuration_groups':'Manage Configuration Groups...',
   'services.service.actions.serviceActions':'Service Actions...',
   'services.service.summary.unknown':'unknown',
   'services.service.summary.notRunning':'Not Running',
@@ -1111,7 +1114,12 @@ Em.I18n.translations = {
   'services.service.add':'Add Service',
   'services.service.startAll':'Start All',
   'services.service.stopAll':'Stop All',
-
+  'services.service.config_groups_popup.header':'Manage {0} Configuration Groups',
+  'services.service.config_groups_popup.notice':'Manage configuration groups of this service. Groups can be created, renamed and deleted and from here. Host memberships can also be changed. A host can belong to only one configuration group.',
+  'services.service.config_groups_popup.config_groups':'Configuration Groups',
+  'services.service.config_groups_popup.rename':'Rename',
+  'services.service.config_groups_popup.duplicate':'Duplicate',
+  'services.service.config_groups_popup.properties':'Properties',
   'services.reassign.closePopup':'Reassign {0} wizard is in progress. It\'s necessary to complete the wizard for Ambari to be in usable state. If you choose to quit, you must follow manual instructions to complete or revert reassign {0} wizard as documented in the Ambari User Guide. Are you sure you want to exit the wizard ?',
 
   'services.reassign.step1.header':'Get Started',

+ 49 - 35
ambari-web/app/models/config_group.js

@@ -19,80 +19,94 @@
 var App = require('app');
 
 /**
- * Represents a configuration-group on the cluster. 
+ * Represents a configuration-group on the cluster.
  * A configuration-group is a collection of hosts
  * on which a collection of configurations are applied.
- * 
- * Configuration group hierarchy is at 2 levels. For 
+ *
+ * Configuration group hierarchy is at 2 levels. For
  * each service there is a 'Default' configuration group
  * containing all hosts not belonging to any group of that
- * service. 
- * 
+ * service.
+ *
  * A default configuration group has child configuration
  * groups which contain configuration overrides (deltas)
- * for a bunch of hosts. This allows different configurations 
+ * for a bunch of hosts. This allows different configurations
  * for different hosts in a heterogeneous cluster environment.
  */
 App.ConfigGroup = Ember.Object.extend({
-  id: DS.attr('number'),
-  name: DS.attr('string'),
-  description: DS.attr('string'),
-  isDefault: DS.attr('boolean'),
-  
+  id: null,
+  name: null,
+  description: null,
+  isDefault: null,
+
   /**
    * Parent configuration group for this group.
    * When {@link #isDefault} is true, this value is <code>null</code>
    * When {@link #isDefault} is false, this represents the configuration
    * deltas that are applied on the default.
    */
-  parentConfigGroup: DS.belongsTo('App.ConfigGroup'),
-  
+  parentConfigGroup: null,
+
   /**
    * Children configuration groups for this group.
    * When {@link #isDefault} is false, this value is <code>null</code>
    * When {@link #isDefault} is true, this represents the various
    * configuration groups that override the default.
    */
-  childConfigGroups: DS.hasMany('App.ConfigGroup'),
-  
+  childConfigGroups: [],
+
   /**
-   * Service for which this configuration-group 
+   * Service for which this configuration-group
    * is applicable.
    */
-  service: DS.belongsTo('App.Service'),
-  
+  service: null,
+
   /**
-   * Hosts on which this configuration-group 
-   * is to be applied. For a service, a host can 
+   * Hosts on which this configuration-group
+   * is to be applied. For a service, a host can
    * belong to only one non-default configuration-group.
-   * 
+   *
    * When {#isDefault} is false, this contains hosts
    * for which the overrides will apply.
-   * 
-   * When {#isDefault} is true, this value is empty, as 
+   *
+   * When {#isDefault} is true, this value is empty, as
    * it dynamically reflects hosts not belonging to other
    * non-default groups.
-   * 
+   *
    */
-  hosts: DS.hasMany('App.Host'),
-  
+  hosts: [],
+
+  displayName: function () {
+    return this.get('name') + ' (' + this.get('hosts.length') + ')';
+  }.property('name', 'hosts.length'),
+
   /**
-   * Provides hosts which are available for inclusion in 
-   * non-default configuration groups. 
+   * Provides hosts which are available for inclusion in
+   * non-default configuration groups.
    */
-  availableHosts: function() {
-    
+  availableHosts: function () {
+
   }.property('isDefault', 'parentConfigGroup', 'childConfigGroups'),
-  
+
   /**
    * Collection of (site, tag) pairs representing properties.
-   * 
-   * When {#isDefault} is true, this represents the 
+   *
+   * When {#isDefault} is true, this represents the
    * default cluster configurations for that service.
-   * 
+   *
    * When {#isDefault} is false, this represents the
    * configuration overrides on top of the cluster default for the
    * hosts identified by 'hosts'.
    */
-  configSiteTags: DS.hasMany('App.ConfigSiteTag')
+  configSiteTags: [],
+
+  properties: [],
+
+  propertiesList: function () {
+    var result = '';
+    this.get('properties').forEach(function (item) {
+      result += item.name + " : " + item.value + '\n';
+    }, this);
+    return result;
+  }.property('properties.length')
 });

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

@@ -4870,3 +4870,9 @@ i.icon-asterisks {
     }
   }
 }
+
+.manage-configuration-group-popup {
+  .group-select {
+    width: 100%
+  }
+}

+ 63 - 0
ambari-web/app/templates/main/service/manage_configuration_groups_popup.hbs

@@ -0,0 +1,63 @@
+{{!
+* 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.
+}}
+<div class="alert alert-info">{{t services.service.config_groups_popup.notice}}</div>
+<div class="row-fluid">
+  <div class="span12">
+    {{t services.service.config_groups_popup.config_groups}}
+    <div class="row-fluid">
+      <div class="span6">
+        {{view Em.Select
+          contentBinding="configGroups"
+          optionLabelPath="content.displayName"
+          selectionBinding="view.selectedConfigGroup"
+          multiple="multiple"
+          class="group-select"
+        }}
+        <div class="button-group pull-right">
+          <a class="btn">+</a>
+          <a class="btn">-</a>
+          <a class="btn">{{t services.service.config_groups_popup.rename}}</a>
+          <a class="btn">{{t services.service.config_groups_popup.duplicate}}</a>
+        </div>
+      </div>
+      <div class="span6">
+        <div class="row-fluid">
+          <div class="span2">{{t common.hosts}}</div>
+          <div class="span10">
+            {{view Em.Select
+              contentBinding="selectedConfigGroup.hosts"
+              multiple="multiple"
+              class="group-select"
+            }}
+          </div>
+          <div class="button-group pull-right">
+            <a class="btn">+</a>
+            <a class="btn">-</a>
+          </div>
+        </div>
+        <div class="row-fluid">
+          <div class="span2">{{t common.overrides}}</div>
+          <div class="span10">
+            <a href="" class="properties-link" {{action showProperties target="controller"}}
+               rel="HealthTooltip" {{bindAttr data-original-title="selectedConfigGroup.propertiesList" }}>{{selectedConfigGroup.properties.length}} {{t common.properties}}</a>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 3 - 0
ambari-web/app/utils/ajax.js

@@ -90,6 +90,9 @@ var urls = {
       };
     }
   },
+  'service.load_config_groups': {
+    'real': '/clusters/{clusterName}/config_groups?ConfigGroup/tag={serviceName}&fields=*'
+  },
   'reassign.stop_services': {
     'real': '/clusters/{clusterName}/services',
     'mock': '',

+ 1 - 0
ambari-web/app/views.js

@@ -196,6 +196,7 @@ require('views/main/service/reassign/step3_view');
 require('views/main/service/reassign/step4_view');
 require('views/main/service/reassign/step5_view');
 require('views/main/service/reassign/step6_view');
+require('views/main/service/manage_config_groups_view');
 require('views/main/charts/menu');
 require('views/main/charts/heatmap');
 require('views/main/charts/heatmap/heatmap_rack');

+ 1 - 0
ambari-web/app/views/main/service/item.js

@@ -43,6 +43,7 @@ App.MainServiceItemView = Em.View.extend({
         }
       default:
         options.push({action: 'runSmokeTest', 'label': Em.I18n.t('services.service.actions.run.smoke'), disabled:disabled});
+        options.push({action: 'manageConfigurationGroups', 'label': Em.I18n.t('services.service.actions.manage_configuration_groups'), disabled:false});
     }
     return options;
   }.property('controller.content', 'controller.isStopDisabled'),

+ 48 - 0
ambari-web/app/views/main/service/manage_config_groups_view.js

@@ -0,0 +1,48 @@
+/**
+ * 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');
+
+App.MainServiceManageConfigGroupView = Em.View.extend({
+
+  templateName: require('templates/main/service/manage_configuration_groups_popup'),
+
+  selectedConfigGroup: null,
+
+  onGroupSelect: function () {
+    var selectedConfigGroup = this.get('selectedConfigGroup');
+    // to unable user select more than one config group at a time
+    if (selectedConfigGroup.length) {
+      this.set('controller.selectedConfigGroup', selectedConfigGroup[selectedConfigGroup.length - 1]);
+    }
+    if (selectedConfigGroup.length > 1) {
+      this.set('selectedConfigGroup', selectedConfigGroup[selectedConfigGroup.length - 1]);
+    }
+  }.observes('selectedConfigGroup'),
+
+  onLoad: function () {
+    if (this.get('controller.isLoaded')) {
+      this.set('selectedConfigGroup', this.get('controller.configGroups')[0])
+    }
+  }.observes('controller.isLoaded', 'controller.configGroups'),
+
+  didInsertElement: function () {
+    this.get('controller').loadConfigGroups(this.get('serviceName'));
+    $('.properties-link').tooltip();
+  }
+});