Browse Source

AMBARI-10304. Create widget wizard: Implement the initial layout of the wizard. (jaimin)

Jaimin Jetly 10 năm trước cách đây
mục cha
commit
8985bcf112
23 tập tin đã thay đổi với 938 bổ sung88 xóa
  1. 4 0
      ambari-web/app/controllers.js
  2. 2 2
      ambari-web/app/controllers/main/service/info/summary.js
  3. 46 0
      ambari-web/app/controllers/main/service/widgets/create/step1_controller.js
  4. 39 0
      ambari-web/app/controllers/main/service/widgets/create/step2_controller.js
  5. 28 0
      ambari-web/app/controllers/main/service/widgets/create/step3_controller.js
  6. 189 0
      ambari-web/app/controllers/main/service/widgets/create/wizard_controller.js
  7. 82 66
      ambari-web/app/data/service_graph_config.js
  8. 12 0
      ambari-web/app/messages.js
  9. 73 0
      ambari-web/app/models/widget.js
  10. 159 0
      ambari-web/app/routes/add_widget.js
  11. 10 0
      ambari-web/app/routes/main.js
  12. 22 16
      ambari-web/app/templates/main/service/info/summary.hbs
  13. 36 0
      ambari-web/app/templates/main/service/widgets/create/step1.hbs
  14. 34 0
      ambari-web/app/templates/main/service/widgets/create/step2.hbs
  15. 27 0
      ambari-web/app/templates/main/service/widgets/create/step3.hbs
  16. 40 0
      ambari-web/app/templates/main/service/widgets/create/wizard.hbs
  17. 1 0
      ambari-web/app/utils/db.js
  18. 5 0
      ambari-web/app/views.js
  19. 17 4
      ambari-web/app/views/main/service/info/summary.js
  20. 32 0
      ambari-web/app/views/main/service/widgets/create/step1_view.js
  21. 29 0
      ambari-web/app/views/main/service/widgets/create/step2_view.js
  22. 27 0
      ambari-web/app/views/main/service/widgets/create/step3_view.js
  23. 24 0
      ambari-web/app/views/main/service/widgets/create/wizard_view.js

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

@@ -105,6 +105,10 @@ 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/service/widgets/create/wizard_controller');
+require('controllers/main/service/widgets/create/step1_controller');
+require('controllers/main/service/widgets/create/step2_controller');
+require('controllers/main/service/widgets/create/step3_controller');
 require('controllers/main/host');
 require('controllers/main/host/details');
 require('controllers/main/host/configs_service');

+ 2 - 2
ambari-web/app/controllers/main/service/info/summary.js

@@ -313,7 +313,7 @@ App.MainServiceInfoSummaryController = Em.Controller.extend({
    * @type {Em.A}
    */
   widgets: function() {
-    return App.Widget.find().filterProperty('serviceName', this.get('content.serviceName'));
+    return App.Widget.find().filterProperty('sectionName', this.get('content.serviceName') + '_SUMMARY');
   }.property('isWidgetsLoaded'),
 
   /**
@@ -426,7 +426,7 @@ App.MainServiceInfoSummaryController = Em.Controller.extend({
    * create widget
    */
   createWidget: function () {
-
+    App.router.send('addServiceWidget', this.get('content'));
   }
 
 });

+ 46 - 0
ambari-web/app/controllers/main/service/widgets/create/step1_controller.js

@@ -0,0 +1,46 @@
+/**
+ * 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.WidgetWizardStep1Controller = Em.Controller.extend({
+  name: "widgetWizardStep1Controller",
+
+  widgetType: '',
+
+  isSubmitDisabled: function() {
+    return !this.get('widgetType');
+  }.property('widgetType'),
+
+  options: App.WidgetType.find(),
+
+  loadStep: function() {
+    this.clearStep();
+  },
+
+  clearStep: function() {
+    this.set('widgetType', '');
+  },
+
+  next: function () {
+    if (!this.get('isSubmitDisabled')) {
+      App.router.send('next');
+    }
+  }
+});
+

+ 39 - 0
ambari-web/app/controllers/main/service/widgets/create/step2_controller.js

@@ -0,0 +1,39 @@
+/**
+ * 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.WidgetWizardStep2Controller = Em.Controller.extend({
+  name: "widgetWizardStep2Controller",
+
+
+  //TODO: Following computed property needs to be implemented. Next button should be enabled when there is no validation error and all required fields are filled
+  isSubmitDisabled: function() {
+    return false;
+  }.property(''),
+
+
+
+
+  next: function () {
+    if (!this.get('isSubmitDisabled')) {
+      App.router.send('next');
+    }
+  }
+});
+

+ 28 - 0
ambari-web/app/controllers/main/service/widgets/create/step3_controller.js

@@ -0,0 +1,28 @@
+/**
+ * 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.WidgetWizardStep3Controller = Em.Controller.extend({
+  name: "widgetWizardStep3Controller",
+
+  //TODO: Following computed propert needs to be implemented. Next button should be enabled when there is no validation error and all required fields are filled
+  isSubmitDisabled: function() {
+    return false;
+  }.property('')
+});

+ 189 - 0
ambari-web/app/controllers/main/service/widgets/create/wizard_controller.js

@@ -0,0 +1,189 @@
+/**
+ * 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.WidgetWizardController = App.WizardController.extend({
+
+  name: 'widgetWizardController',
+
+  totalSteps: 3,
+
+  /**
+   * Used for hiding back button in wizard
+   */
+  hideBackButton: true,
+
+
+  content: Em.Object.create({
+    controllerName: 'widgetWizardController',
+    widgetService: null,
+    widgetType: '',
+    widgetProperties: [],
+    widgetMetrics: [],
+    widgetValues: [],
+    widgetName: null,
+    widgetDescription: null,
+    widgetScope: null
+  }),
+
+  /**
+   * set current step
+   * @param {string} currentStep
+   * @param {boolean} completed
+   * @param {boolean} skipStateSave
+   */
+  setCurrentStep: function (currentStep, completed, skipStateSave) {
+    this._super(currentStep, completed);
+    if (App.get('testMode') || skipStateSave) {
+      return;
+    }
+    App.clusterStatus.setClusterStatus({
+      clusterName: this.get('content.cluster.name'),
+      clusterState: 'WIDGET_DEPLOY',
+      wizardControllerName: 'widgetWizardController',
+      localdb: App.db.data
+    });
+  },
+
+  setStepsEnable: function () {
+    for (var i = 1; i <= this.get('totalSteps'); i++) {
+      var step = this.get('isStepDisabled').findProperty('step', i);
+      if (i <= this.get('currentStep') && App.get('router.clusterController.isLoaded')) {
+        step.set('value', false);
+      } else {
+        step.set('value', i != this.get('currentStep'));
+      }
+    }
+  }.observes('currentStep', 'App.router.clusterController.isLoaded'),
+
+
+
+  /**
+   * save status of the cluster.
+   * @param clusterStatus object with status,requestId fields.
+   */
+  saveClusterStatus: function (clusterStatus) {
+    var oldStatus = this.toObject(this.get('content.cluster'));
+    clusterStatus = jQuery.extend(oldStatus, clusterStatus);
+    if (clusterStatus.requestId) {
+      clusterStatus.requestId.forEach(function (requestId) {
+        if (clusterStatus.oldRequestsId.indexOf(requestId) === -1) {
+          clusterStatus.oldRequestsId.push(requestId)
+        }
+      }, this);
+    }
+    this.set('content.cluster', clusterStatus);
+    this.save('cluster');
+  },
+
+  loadWidgetService: function() {
+    this.set('content.widgetService', this.getDBProperty('widgetService'));
+  },
+
+  loadWidgetType: function() {
+    this.set('content.widgetType', this.getDBProperty('widgetType'));
+  },
+
+  loadWidgetProperties: function() {
+    this.set('content.widgetProperties', this.getDBProperty('widgetProperties'));
+  },
+
+  loadWidgetMetrics: function() {
+    this.set('content.widgetMetrics', this.getDBProperty('widgetMetrics'));
+  },
+
+  loadWidgetValues: function() {
+    this.set('content.widgetValues', this.getDBProperty('widgetValues'));
+  },
+
+
+  saveWidgetService: function(widgetService) {
+    this.setDBProperty('widgetService',widgetService);
+    this.set('content.widgetService', widgetService);
+  },
+
+  saveWidgetType: function(widgetType) {
+    this.setDBProperty('widgetType',widgetType);
+    this.set('content.widgetType', widgetType);
+  },
+
+  saveWidgetProperties: function(widgetProperties) {
+    this.setDBProperty('widgetProperties',widgetProperties);
+    this.set('content.widgetProperties', widgetProperties);
+  },
+
+  saveWidgetMetrics: function(widgetMetrics) {
+    this.setDBProperty('widgetMetrics',widgetMetrics);
+    this.set('content.widgetMetrics', widgetMetrics);
+  },
+
+  saveWidgetValues: function(widgetValues) {
+    this.setDBProperty('widgetValues',widgetValues);
+    this.set('content.widgetValues', widgetValues);
+  },
+
+  loadMap: {
+    '1': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.loadWidgetService();
+          this.loadWidgetType();
+        }
+      }
+    ],
+    '2': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.loadWidgetProperties();
+          this.loadWidgetMetrics();
+          this.loadWidgetValues();
+        }
+      }
+    ]
+  },
+
+  /**
+   * Remove all loaded data.
+   * Created as copy for App.router.clearAllSteps
+   */
+  clearAllSteps: function () {
+    this.clearInstallOptions();
+    // clear temporary information stored during the install
+    this.set('content.cluster', this.getCluster());
+  },
+
+  clearTasksData: function () {
+    this.saveTasksStatuses(undefined);
+    this.saveRequestIds(undefined);
+    this.saveTasksRequestIds(undefined);
+  },
+
+  /**
+   * Clear all temporary data
+   */
+  finish: function () {
+    this.setCurrentStep('1', false, true);
+    this.saveWidgetType('');
+    this.resetDbNamespace();
+    App.get('router.updateController').updateAll();
+  }
+});

+ 82 - 66
ambari-web/app/data/service_graph_config.js

@@ -19,79 +19,95 @@
 var App = require('app');
 
 /**
-This determines the graphs to display on the service page under each service.
+ This determines the graphs to display on the service page under each service.
 
-This is based on the name of the object associated with it.
+ This is based on the name of the object associated with it.
 
-The name of the object is of the format: 'App.ChartServiceMetrics<name>' where <name>
-is one of the items below.
-**/
-App.service_graph_config = {
-	'hdfs': [
-		'HDFS_SpaceUtilization',
-		'HDFS_FileOperations',
-		'HDFS_BlockStatus',
-		'HDFS_IO',
-		'HDFS_RPC',
-		'HDFS_GC',
-		'HDFS_JVMHeap',
-		'HDFS_JVMThreads'
-	],
+ The name of the object is of the format: 'App.ChartServiceMetrics<name>' where <name>
+ is one of the items below.
+ **/
 
-	'yarn': [
-		'YARN_AllocatedMemory',
-		'YARN_QMR',
-		'YARN_AllocatedContainer',
-		'YARN_NMS',
-		'YARN_ApplicationCurrentStates',
-		'YARN_ApplicationFinishedStates',
-		'YARN_RPC',
-		'YARN_GC',
-		'YARN_JVMThreads',
-		'YARN_JVMHeap'
-	],
+module.exports = {
+  allServices: {
+    'hdfs': [
+      'HDFS_SpaceUtilization',
+      'HDFS_FileOperations',
+      'HDFS_BlockStatus',
+      'HDFS_IO',
+      'HDFS_RPC',
+      'HDFS_GC',
+      'HDFS_JVMHeap',
+      'HDFS_JVMThreads'
+    ],
 
-	'hbase': [
-		'HBASE_ClusterRequests',
-		'HBASE_RegionServerReadWriteRequests',
-		'HBASE_RegionServerRegions',
-		'HBASE_RegionServerQueueSize',
-		'HBASE_HlogSplitTime',
-		'HBASE_HlogSplitSize'
-	],
+    'yarn': [
+      'YARN_AllocatedMemory',
+      'YARN_QMR',
+      'YARN_AllocatedContainer',
+      'YARN_NMS',
+      'YARN_ApplicationCurrentStates',
+      'YARN_ApplicationFinishedStates',
+      'YARN_RPC',
+      'YARN_GC',
+      'YARN_JVMThreads',
+      'YARN_JVMHeap'
+    ],
 
-  'ambari_metrics': [
-    'AMS_MasterAverageLoad',
-    'AMS_RegionServerStoreFiles',
-    'AMS_RegionServerRegions',
-    'AMS_RegionServerRequests',
-    'AMS_RegionServerBlockCacheHitPercent',
-    'AMS_RegionServerCompactionQueueSize'
-  ],
+    'hbase': [
+      'HBASE_ClusterRequests',
+      'HBASE_RegionServerReadWriteRequests',
+      'HBASE_RegionServerRegions',
+      'HBASE_RegionServerQueueSize',
+      'HBASE_HlogSplitTime',
+      'HBASE_HlogSplitSize'
+    ],
 
-	'flume': [
-		'Flume_ChannelSizeMMA',
-		'Flume_ChannelSizeSum',
-		'Flume_IncommingMMA',
-		'Flume_IncommingSum',
-		'Flume_OutgoingMMA',
-		'Flume_OutgoingSum'
-	],
+    'ambari_metrics': [
+      'AMS_MasterAverageLoad',
+      'AMS_RegionServerStoreFiles',
+      'AMS_RegionServerRegions',
+      'AMS_RegionServerRequests',
+      'AMS_RegionServerBlockCacheHitPercent',
+      'AMS_RegionServerCompactionQueueSize'
+    ],
 
-	'storm': [
-		'STORM_SlotsNumber',
-		'STORM_Executors',
-		'STORM_Topologies',
-		'STORM_Tasks'
-	],
+    'flume': [
+      'Flume_ChannelSizeMMA',
+      'Flume_ChannelSizeSum',
+      'Flume_IncommingMMA',
+      'Flume_IncommingSum',
+      'Flume_OutgoingMMA',
+      'Flume_OutgoingSum'
+    ],
 
-  'kafka': [
-    'Kafka_BrokerTopicMetrics',
-    'Kafka_Controller',
-    'Kafka_ControllerStatus',
-    'Kafka_ReplicaManager',
-    'Kafka_LogFlush',
-    'Kafka_ReplicaFetcher'
-  ]
+    'storm': [
+      'STORM_SlotsNumber',
+      'STORM_Executors',
+      'STORM_Topologies',
+      'STORM_Tasks'
+    ],
+
+    'kafka': [
+      'Kafka_BrokerTopicMetrics',
+      'Kafka_Controller',
+      'Kafka_ControllerStatus',
+      'Kafka_ReplicaManager',
+      'Kafka_LogFlush',
+      'Kafka_ReplicaFetcher'
+    ]
+  },
+
+  getServiceGraphConfig: function () {
+    if (App.get('supports.customizedWidgets')) {
+      var servicesWithEnhancedDashboard = ['hdfs', 'yarn', 'hbase'];
+      var newServiceObject = jQuery.extend(true, {}, this.allServices);
+      servicesWithEnhancedDashboard.forEach(function (_serviceName) {
+        newServiceObject[_serviceName] = [];
+      });
+      return newServiceObject;
+    } else {
+      return this.allServices;
+    }
+  }
 
 };

+ 12 - 0
ambari-web/app/messages.js

@@ -2461,6 +2461,18 @@ Em.I18n.translations = {
   'rolling.nothingToDo.header': 'Nothing to do',
   'rolling.nothingToDo.body': '{0} on selected hosts are already in selected state or in Maintenance Mode.',
 
+  'widget.type.gauge.description': 'A view to display metrics that can be expressed in percentage.',
+  'widget.type.number.description': 'A view to display metrics that can be expressed as a single number with optional unit field.',
+  'widget.type.graph.description': 'A view to display metrics that can be expressed in line graph or area graph over a time range.',
+  'widget.type.template.description': 'A view to display metric value along with a templated text.',
+  'widget.create.wizard.header': 'Create Widget',
+  'widget.create.wizard.step1.header': 'Choose Type',
+  'widget.create.wizard.step1.body.text': 'What type of widget do you want to create?',
+  'widget.create.wizard.step2.header': 'Metrics and Expression',
+  'widget.create.wizard.step2.body.text':'Define the expression with any metrics and valid operators. </br>Use parentheses when necessary.',
+  'widget.create.wizard.step2.body.warning':'Note: Valid operators are +, -, *, /',
+  'widget.create.wizard.step3.header': 'Name and Description',
+
   'restart.service.all': 'Restart All',
   'restart.service.all.affected': 'Restart All Affected',
   'restart.service.rest.context': 'Restart {0}s',

+ 73 - 0
ambari-web/app/models/widget.js

@@ -67,5 +67,78 @@ App.Widget = DS.Model.extend({
   }.property('widgetType')
 });
 
+App.WidgetType = DS.Model.extend({
+  name: DS.attr('string'),
+  displayName: DS.attr('string'),
+  description: DS.attr('string'),
+  properties: DS.attr('array')
+});
+
 
 App.Widget.FIXTURES = [];
+
+App.WidgetType.FIXTURES = [
+  {
+    id: 1,
+    name: 'GAUGE',
+    display_name: 'Gauge',
+    description: Em.I18n.t('widget.type.gauge.description'),
+    properties: [
+      {
+        property_name : 'warning_threshold',
+        isRequired: true   // This field is used to distinguish required properties from optional. This can be used for imposing client side validation
+      },
+      {
+        property_name : 'error_threshold',
+        isRequired: true
+      }
+    ]
+  },
+  {
+    id: 2,
+    name: 'NUMBER',
+    display_name: 'Number',
+    description: Em.I18n.t('widget.type.number.description'),
+    properties: [
+      {
+        property_name : 'warning_threshold',
+        display_name: 'warning',
+        isRequired: false
+      },
+      {
+        property_name : 'error_threshold',
+        display_name: 'critical',
+        isRequired: false
+      },
+      {
+        property_name : 'display_unit',
+        display_name: 'unit',
+        isRequired: false
+      }
+    ]
+  },
+  {
+    id: 3,
+    name: 'GRAPH',
+    display_name: 'Graph',
+    description: Em.I18n.t('widget.type.graph.description'),
+    properties: [
+      {
+        property_name : 'graph_type',
+        isRequired: true
+      },
+      {
+        property_name : 'time_range',
+        isRequired: true
+      }
+    ]
+  },
+  {
+    id: 4,
+    name: 'TEMPLATE',
+    display_name: 'Template',
+    description: Em.I18n.t('widget.type.template.description'),
+    properties: [
+    ]
+  }
+];

+ 159 - 0
ambari-web/app/routes/add_widget.js

@@ -0,0 +1,159 @@
+/**
+ * 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');
+
+module.exports = App.WizardRoute.extend({
+  route: '/widget/add',
+  enter: function (router, context) {
+    router.get('mainController').dataLoading().done(function () {
+      var widgetWizardController = router.get('widgetWizardController');
+      App.router.get('updateController').set('isWorking', false);
+      var popup = App.ModalPopup.show({
+        classNames: ['full-width-modal'],
+        header: Em.I18n.t('widget.create.wizard.header'),
+        bodyClass: App.WidgetWizardView.extend({
+          controller: widgetWizardController
+        }),
+        primary: Em.I18n.t('form.cancel'),
+        showFooter: false,
+        secondary: null,
+
+        onClose: function () {
+          var self = this;
+          widgetWizardController.finish();
+          if (App.testMode) {
+            self.hide();
+            var serviceName = widgetWizardController.get('content.widgetService');
+            var service = App.Service.find().findProperty('serviceName', serviceName);
+            router.transitionTo('main.services.service', service);
+          }   else {
+            App.clusterStatus.setClusterStatus({
+              clusterName: App.router.getClusterName(),
+              clusterState: 'DEFAULT',
+              localdb: App.db.data
+            }, {
+              alwaysCallback: function () {
+                self.hide();
+                var serviceName = widgetWizardController.get('content.widgetService');
+                var service = App.Service.find().findProperty('serviceName', serviceName);
+                router.transitionTo('main.services.service', service);
+              }
+            });
+          }
+        },
+
+        didInsertElement: function () {
+          this.fitHeight();
+        }
+
+      });
+      widgetWizardController.set('popup', popup);
+      var currentClusterStatus = App.clusterStatus.get('value');
+      if (currentClusterStatus) {
+        if (App.get('testMode')) {
+          widgetWizardController.setCurrentStep(App.db.data.WidgetWizard.currentStep);
+        } else {
+          var currStep = App.get('router.widgetWizardController.currentStep');
+          widgetWizardController.setCurrentStep(currStep);
+        }
+      }
+      Em.run.next(function () {
+        router.transitionTo('step' + widgetWizardController.get('currentStep'));
+      });
+    });
+  },
+
+  step1: Em.Route.extend({
+    route: '/step1',
+
+    connectOutlets: function (router) {
+      var controller = router.get('widgetWizardController');
+      controller.dataLoading().done(function () {
+        router.get('widgetWizardController').setCurrentStep('1');
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('widgetWizardStep1', controller.get('content'));
+      });
+    },
+
+    unroutePath: function () {
+      return false;
+    },
+
+    next: function (router) {
+      var widgetWizardController = router.get('widgetWizardController');
+      var widgetStep1controller = router.get('widgetWizardStep1Controller');
+      widgetWizardController.saveWidgetType(widgetStep1controller.get('widgetType'));
+      widgetWizardController.setDBProperty('widgetProperties', []);
+      widgetWizardController.setDBProperty('widgetMetrics', []);
+      widgetWizardController.setDBProperty('widgetValues', []);
+      router.transitionTo('step2');
+    }
+  }),
+
+  step2: Em.Route.extend({
+    route: '/step2',
+
+    connectOutlets: function (router) {
+      var controller = router.get('widgetWizardController');
+      controller.dataLoading().done(function () {
+        router.get('widgetWizardController').setCurrentStep('2');
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('widgetWizardStep2', controller.get('content'));
+      });
+    },
+    unroutePath: function () {
+      return false;
+    },
+    back: Em.Router.transitionTo('step1'),
+
+    next: function (router) {
+      var widgetWizardController = router.get('widgetWizardController');
+      var widgetStep2controller = router.get('widgetWizardStep2Controller');
+      widgetWizardController.saveWidgetProperties(widgetStep2controller.get('widgetProperties'));
+      widgetWizardController.saveWidgetMetrics(widgetStep2controller.get('widgetMetrics'));
+      widgetWizardController.saveWidgetValues(widgetStep2controller.get('widgetValues'));
+      widgetWizardController.setDBProperty('widgetName', null);
+      widgetWizardController.setDBProperty('widgetDescription', null);
+      widgetWizardController.setDBProperty('widgetScope', null);
+      router.transitionTo('step3');
+    }
+  }),
+
+  step3: Em.Route.extend({
+    route: '/step3',
+
+    connectOutlets: function (router) {
+      var controller = router.get('widgetWizardController');
+      controller.dataLoading().done(function () {
+        router.get('widgetWizardController').setCurrentStep('3');
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('widgetWizardStep3', controller.get('content'));
+      });
+    },
+    unroutePath: function () {
+      return false;
+    },
+    back: Em.Router.transitionTo('step2'),
+    complete: function (router, context) {
+      var controller = router.get('widgetWizardStep3Controller');
+      if (!controller.get('isSubmitDisabled')) {
+        $(context.currentTarget).parents("#modal").find(".close").trigger('click');
+      }
+    }
+  })
+});

+ 10 - 0
ambari-web/app/routes/main.js

@@ -561,6 +561,16 @@ module.exports = Em.Route.extend(App.RouterRedirections, {
 
   }),
 
+  addServiceWidget: function (router, context) {
+    if (context) {
+      var widgetController = router.get('widgetWizardController');
+      widgetController.saveWidgetService(context.get('serviceName'));
+    }
+    router.transitionTo('addWidget');
+  },
+
+  addWidget: require('routes/add_widget'),
+
   services: Em.Route.extend({
     route: '/services',
     index: Em.Route.extend({

+ 22 - 16
ambari-web/app/templates/main/service/info/summary.hbs

@@ -31,7 +31,9 @@
             <span class="caret"></span>
           </button>
           <ul class="dropdown-menu">
-            <li><a href="#" {{action restartAllStaleConfigComponents target="view"}}>{{t restart.service.all.affected}}</a></li>
+            <li>
+              <a href="#" {{action restartAllStaleConfigComponents target="view"}}>{{t restart.service.all.affected}}</a>
+            </li>
             {{#if view.rollingRestartSlaveComponentName}}
               <li>
                 <a href="#" {{action rollingRestartStaleConfigSlaveComponents view.rollingRestartSlaveComponentName target="view"}}>{{view.rollingRestartActionName}}</a>
@@ -55,7 +57,7 @@
             {{view.alertsCount}} {{pluralize view.alertsCount singular="alert" plural="alerts"}}</span>
         {{else}}
           <span {{action "showServiceAlertsPopup" controller.content target="controller"}}
-            class="label pull-right no-alerts-label">{{t services.service.summary.alerts.noAlerts}}</span>
+                  class="label pull-right no-alerts-label">{{t services.service.summary.alerts.noAlerts}}</span>
         {{/if}}
       {{/if}}
     </div>
@@ -78,13 +80,14 @@
         <div class="box">
           <div class="box-header">
             <h4>{{t services.service.metrics}}</h4>
+
             <div class="btn-group pull-right">
               <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
                 {{view.currentTimeRange.name}} &nbsp;<span class="caret"></span>
               </button>
               <ul class="dropdown-menu">
                 {{#each option in view.timeRangeOptions}}
-                    <li><a href="#" {{action setTimeRange option target="view"}}>{{option.name}}</a></li>
+                  <li><a href="#" {{action setTimeRange option target="view"}}>{{option.name}}</a></li>
                 {{/each}}
               </ul>
             </div>
@@ -97,7 +100,8 @@
                   {{#each option in view.widgetActions}}
                     <li {{bindAttr class="option.layouts:dropdown-submenu"}}>
                       {{#if option.isAction}}
-                        <a href="javascript:void(0);" class="action" {{action doWidgetAction option.action target="view"}}>
+                        <a href="javascript:void(0);"
+                           class="action" {{action doWidgetAction option.action target="view"}}>
                           <i {{bindAttr class="option.class"}}></i>
                           {{option.label}}
                         </a>
@@ -139,23 +143,25 @@
           <div class="">
             <table class="graphs">
               {{#if App.supports.customizedWidgets}}
-                <tr id="widget_layout">
-                  {{#each widget in controller.widgets}}
-                    <td>
-                      <div class="widget">
-                        {{view widget.viewClass contentBinding="widget"}}
-                      </div>
-                    </td>
-                  {{/each}}
-                </tr>
+                {{#if controller.isWidgetsLoaded}}
+                  <tr id="widget_layout">
+                    {{#each widget in controller.widgets}}
+                      <td>
+                        <div class="widget">
+                          {{view widget.viewClass contentBinding="widget"}}
+                        </div>
+                      </td>
+                    {{/each}}
+                  </tr>
+                {{/if}}
               {{/if}}
               {{#each graphs in view.serviceMetricGraphs}}
                 <tr>
                   {{#each graph in graphs}}
                     <td>
-                        <div class="">
-                          {{view graph}}
-                        </div>
+                      <div class="">
+                        {{view graph}}
+                      </div>
                     </td>
                   {{/each}}
                 </tr>

+ 36 - 0
ambari-web/app/templates/main/service/widgets/create/step1.hbs

@@ -0,0 +1,36 @@
+{{!
+* 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 id="add-widget-step1">
+  <h2>{{t widget.create.wizard.step1.header}}</h2>
+
+  <div id="add-widget-step1-options-body">
+    {{t widget.create.wizard.step1.body.text}}
+    {{#each option in options}}
+      <label class="checkbox add-widget-step1-option-radiobutton">
+        {{view Ember.RadioButton name="option.displayName" selectionBinding="widgetType" valueBinding="option.name"}}  {{option.displayName}} &nbsp;<span class="muted">{{option.description}} </span>
+      </label>
+    {{/each}}
+    <br/>
+  </div>
+
+
+  <div class="btn-area">
+    <a id="add-widget-step1-next" class="btn btn-success pull-right" {{bindAttr disabled="isSubmitDisabled"}} {{action "next" target="controller"}}>{{t common.next}} &rarr;</a>
+  </div>
+</div>

+ 34 - 0
ambari-web/app/templates/main/service/widgets/create/step2.hbs

@@ -0,0 +1,34 @@
+{{!
+* 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 id="add-widget-step2">
+  <h2>{{t widget.create.wizard.step2.header}}</h2>
+  <div class="alert alert-info">
+    {{t widget.create.wizard.step2.body.text}}
+    <div class="alert alert-warning">
+      {{t widget.create.wizard.step2.body.warning}}
+    </div>
+  </div>
+
+
+
+  <div class="btn-area">
+    <a id="add-widget-step2-back" class="btn" {{action back}}>&larr; {{t common.back}}</a>
+    <a id="add-widget-step2-next" class="btn btn-success pull-right" {{bindAttr disabled="isSubmitDisabled"}} {{action "next" target="controller"}}>{{t common.next}} &rarr;</a>
+  </div>
+</div>

+ 27 - 0
ambari-web/app/templates/main/service/widgets/create/step3.hbs

@@ -0,0 +1,27 @@
+{{!
+* 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 id="add-widget-step3">
+  <h2>{{t widget.create.wizard.step3.header}}</h2>
+
+
+  <div class="btn-area">
+    <a id="add-widget-step3-back" class="btn" {{action back}}>&larr; {{t common.back}}</a>
+    <a id="add-widget-step3-complete" class="btn btn-success pull-right" {{bindAttr disabled="isSubmitDisabled"}} {{action "complete"}}>{{t common.complete}}</a>
+  </div>
+</div>

+ 40 - 0
ambari-web/app/templates/main/service/widgets/create/wizard.hbs

@@ -0,0 +1,40 @@
+{{!
+* 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="wizard">
+  <div class="container">
+    <div class="container-fluid">
+      <div class="row-fluid">
+        <div class="span3">
+          <!--Sidebar content-->
+          <div class="well">
+            <ul class="nav nav-pills nav-stacked">
+              <li class="nav-header"> {{t widget.create.wizard.header}}</li>
+              <li {{bindAttr class="isStep1:active view.isStep1Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep1 target="controller"}}>{{t widget.create.wizard.step1.header}}</a></li>
+              <li {{bindAttr class="isStep2:active view.isStep2Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep2 target="controller"}}>{{t widget.create.wizard.step2.header}}</a></li>
+              <li {{bindAttr class="isStep3:active view.isStep3Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep3 target="controller"}}>{{t widget.create.wizard.step3.header}}</a></li>
+            </ul>
+          </div>
+        </div>
+        <div class="wizard-content well span9">
+          {{outlet}}
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 1 - 0
ambari-web/app/utils/db.js

@@ -34,6 +34,7 @@ var InitialData =  {
   'Installer' : {},
   'AddHost' : {},
   'AddService' : {},
+  'WidgetWizard' : {},
   'KerberosWizard': {},
   'StackUpgrade' : {},
   'ReassignMaster' : {},

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

@@ -312,6 +312,11 @@ require('views/main/charts/heatmap/heatmap_rack');
 require('views/main/charts/heatmap/heatmap_host');
 require('views/main/charts/heatmap/heatmap_host_detail');
 
+require('views/main/service/widgets/create/wizard_view');
+require('views/main/service/widgets/create/step1_view');
+require('views/main/service/widgets/create/step2_view');
+require('views/main/service/widgets/create/step3_view');
+
 require('views/main/views_view');
 
 require('views/installer');

+ 17 - 4
ambari-web/app/views/main/service/info/summary.js

@@ -308,7 +308,15 @@ App.MainServiceInfoSummaryView = Em.View.extend(App.UserPref, {
     var result = [], graphObjects = [], chunkSize = this.get('chunkSize');
     var self = this;
 
-    if (!graphNames) {
+    if (App.get('supports.customizedWidgets')) {
+      var serviceName = this.get('controller.content.serviceName');
+      var stackService = App.StackService.find().findProperty('serviceName', serviceName);
+      if (!graphNames && !stackService.get('isServiceWithWidgets')) {
+        self.set('serviceMetricGraphs', []);
+        self.set('isServiceMetricLoaded', true);
+        return;
+      }
+    } else if (!graphNames) {
       self.set('serviceMetricGraphs', []);
       self.set('isServiceMetricLoaded', true);
       return;
@@ -325,7 +333,10 @@ App.MainServiceInfoSummaryView = Em.View.extend(App.UserPref, {
       if (App.get('supports.customizedWidgets')) {
         graphObjects.push(Ember.View.extend({
           classNames: ['last-child'],
-          template: Ember.Handlebars.compile('<div id="add-widget-action-box"><i class="icon-plus"></i></div>')
+          template: Ember.Handlebars.compile('<button id="add-widget-action-box" class="btn btn-default" {{action "goToAddWidgetWizard" controller.content target="view"}}><i class="icon-plus"></i></button>'),
+          goToAddWidgetWizard: function(evt) {
+            App.router.send('addServiceWidget',evt.context);
+          }
         }));
       }
 
@@ -448,7 +459,8 @@ App.MainServiceInfoSummaryView = Em.View.extend(App.UserPref, {
       var svcName = self.get('service.serviceName');
       if (svcName) {
         var result = [], graphObjects = [], chunkSize = this.get('chunkSize');
-        App.service_graph_config[svcName.toLowerCase()].forEach(function(graphName) {
+        var allServices = require('data/service_graph_config').getServiceGraphConfig();
+        allServices[svcName.toLowerCase()].forEach(function(graphName) {
           graphObjects.push(App["ChartServiceMetrics" + graphName].extend({
             currentTimeIndex : event.context.index
           }));
@@ -535,7 +547,8 @@ App.MainServiceInfoSummaryView = Em.View.extend(App.UserPref, {
     }
 
     if (svcName && isMetricsSupported) {
-      this.constructGraphObjects(App.service_graph_config[svcName.toLowerCase()]);
+      var allServices =  require('data/service_graph_config').getServiceGraphConfig();
+      this.constructGraphObjects(allServices[svcName.toLowerCase()]);
     }
     // adjust the summary table height
     var summaryTable = document.getElementById('summary-info');

+ 32 - 0
ambari-web/app/views/main/service/widgets/create/step1_view.js

@@ -0,0 +1,32 @@
+/**
+ * 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.WidgetWizardStep1View = Em.View.extend({
+
+  templateName: require('templates/main/service/widgets/create/step1'),
+
+  didInsertElement: function () {
+    var controller = this.get('controller');
+    controller.loadStep();
+  }
+
+});
+
+

+ 29 - 0
ambari-web/app/views/main/service/widgets/create/step2_view.js

@@ -0,0 +1,29 @@
+/**
+ * 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.
+ */
+
+App.WidgetWizardStep2View = Em.View.extend({
+
+  templateName: require('templates/main/service/widgets/create/step2'),
+
+  didInsertElement: function () {
+    var controller = this.get('controller');
+  }
+
+});
+
+

+ 27 - 0
ambari-web/app/views/main/service/widgets/create/step3_view.js

@@ -0,0 +1,27 @@
+/**
+ * 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.
+ */
+
+App.WidgetWizardStep3View = Em.View.extend({
+
+  templateName: require('templates/main/service/widgets/create/step3'),
+
+  didInsertElement: function () {
+    var controller = this.get('controller');
+  }
+
+});

+ 24 - 0
ambari-web/app/views/main/service/widgets/create/wizard_view.js

@@ -0,0 +1,24 @@
+/**
+ * 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.WidgetWizardView = Em.View.extend(App.WizardMenuMixin, {
+
+  templateName: require('templates/main/service/widgets/create/wizard')
+});