Ver Fonte

AMBARI-10649 Implement ability to edit existing widget definition on a section. (atkach)

Andrii Tkach há 10 anos atrás
pai
commit
24672f0ffa

+ 17 - 0
ambari-web/app/assets/data/widget_layouts/HBASE/default_dashboard.json

@@ -146,6 +146,23 @@
               "values": "[{\"name\":\"CPU Idle\",\"value\":\"${cpu_idle}\"},{\"name\":\"Disk Free\",\"value\":\"${disk_free}\"},{\"name\":\"Network Packets In/Out\",\"value\":\"${pkts_in + pkts_out}\"}]",
               "cluster_name": "c1"
             }
+          },
+          {
+            "href": "http://c6401.ambari.apache.org:8080/api/v1/clusters/c1/widgets/30",
+            "WidgetInfo": {
+              "id": 37,
+              "metrics": "[{\"name\":\"regionserver.Server.Get_95th_percentile\",\"metric_path\":\"metrics/hbase/regionserver/Server/Get_95th_percentile\",\"service_name\":\"HBASE\",\"component_name\":\"HBASE_REGIONSERVER\"},{\"name\":\"regionserver.Server.ScanNext_95th_percentile\",\"metric_path\":\"metrics/hbase/regionserver/Server/ScanNext_95th_percentile\",\"service_name\":\"HBASE\",\"component_name\":\"HBASE_REGIONSERVER\"}]",
+              "author": "ambari",
+              "description": "This widget shows 95th percentile of the read latency.",
+              "scope": "CLUSTER",
+              "properties": "{\"display_unit\":\"%\"}",
+              "widget_name": "READ_LATENCY_97",
+              "widget_type": "TEMPLATE",
+              "time_created": 1428990958952,
+              "display_name": "95% Read Latency",
+              "values": "[{\"name\":\"95% Read Latency\",\"value\":\"${regionserver.Server.Get_95th_percentile+regionserver.Server.ScanNext_95th_percentile}/${regionserver.Server.Get_95th_percentile}\"}]",
+              "cluster_name": "c1"
+            }
           }
         ]
       }

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

@@ -109,6 +109,7 @@ 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/service/widgets/edit_controller');
 require('controllers/main/host');
 require('controllers/main/host/details');
 require('controllers/main/host/configs_service');

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

@@ -681,6 +681,14 @@ App.MainServiceInfoSummaryController = Em.Controller.extend({
     App.router.send('addServiceWidget', this.get('content'));
   },
 
+  /**
+   * edit widget
+   * @param {App.Widget} content
+   */
+  editWidget: function (content) {
+    App.router.send('editServiceWidget', content);
+  },
+
   /**
    * launch Widgets Browser popup
    * @method showPopup

+ 125 - 1
ambari-web/app/controllers/main/service/widgets/create/step2_controller.js

@@ -23,6 +23,19 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
 
   EXPRESSION_PREFIX: 'Expression',
 
+  /**
+   * @type {RegExp}
+   * @const
+   */
+  EXPRESSION_REGEX: /\$\{([\w\s\.\,\+\-\*\/\(\)\:\=\[\]]*)\}/g,
+
+  /**
+   * list of operators that can be used in expression
+   * @type {Array}
+   * @constant
+   */
+  OPERATORS: ["+", "-", "*", "/", "(", ")"],
+
   /**
    * actual values of properties in API format
    * @type {object}
@@ -132,6 +145,7 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
    * Add data set
    * @param {object|null} event
    * @param {boolean} isDefault
+   * @returns {number} id
    */
   addDataSet: function(event, isDefault) {
     var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('dataSets').mapProperty('id')) + 1);
@@ -145,6 +159,7 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
         editMode: false
       }
     }));
+    return id;
   },
 
   /**
@@ -159,6 +174,7 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
    * Add expression
    * @param {object|null} event
    * @param {boolean} isDefault
+   * @returns {number} id
    */
   addExpression: function(event, isDefault) {
     var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('expressions').mapProperty('id')) + 1);
@@ -170,6 +186,7 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
       alias: '{{' + this.get('EXPRESSION_PREFIX') + id + '}}',
       editMode: false
     }));
+    return id;
   },
 
   /**
@@ -205,7 +222,7 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
 
   /**
    * update preview widget with latest expression data
-   * @param {Em.View} view
+   * Note: in order to draw widget it should be converted to API format of widget
    */
   updateExpressions: function () {
     var widgetType = this.get('content.widgetType');
@@ -431,6 +448,113 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
     ];
   },
 
+  /**
+   * convert data with model format to editable format
+   * Note: in order to edit widget expression it should be converted to editable format
+   * @param {App.Widget} content
+   * @param {Ember.Controller} widgetController
+   */
+  convertData: function(content, widgetController) {
+    var self = this;
+    var expressionId = 0;
+
+    this.get('expressions').clear();
+    this.get('dataSets').clear();
+
+    switch (content.get('widgetType')) {
+      case 'NUMBER':
+      case 'GAUGE':
+        var id = this.addExpression(null, true);
+        this.get('expressions').findProperty('id', id).set('data', this.parseValue(content.get('values')[0].value, content.get('metrics'))[0]);
+        break;
+      case 'TEMPLATE':
+        this.parseValue(content.get('values')[0].value, content.get('metrics')).forEach(function(item, index) {
+          var id = this.addExpression(null, (index === 0));
+          this.get('expressions').findProperty('id', id).set('data', item);
+        }, this);
+        this.set('templateValue', content.get('values')[0].value.replace(this.get('EXPRESSION_REGEX'), function(){
+          return '{{' + self.get('EXPRESSION_PREFIX') + ++expressionId + '}}';
+        }));
+        break;
+      case 'GRAPH':
+        content.get('values').forEach(function (value, index) {
+          var id = this.addDataSet(null, (index === 0));
+          var dataSet = this.get('dataSets').findProperty('id', id);
+          dataSet.set('label', value.name);
+          dataSet.set('expression.data', this.parseValue(value.value, content.get('metrics'))[0]);
+        }, this);
+        break;
+    }
+    widgetController.save('templateValue', this.get('templateValue'));
+    widgetController.save('expressions', this.get('expressions'));
+    widgetController.save('dataSets', this.get('dataSets'));
+  },
+
+  /**
+   * parse value
+   * @param value
+   * @param metrics
+   * @returns {Array}
+   */
+  parseValue: function(value, metrics) {
+    var pattern = this.get('EXPRESSION_REGEX'),
+      expressions = [],
+      match;
+
+    while (match = pattern.exec(value)) {
+      expressions.push(this.getExpressionData(match[1], metrics));
+    }
+
+    return expressions;
+  },
+
+  /**
+   * format values into expression data objects
+   * @param {string} expression
+   * @param {Array} metrics
+   * @returns {Array}
+   */
+  getExpressionData: function(expression, metrics) {
+    var str = '';
+    var data = [];
+    var id = 0;
+    var metric;
+
+    for (var i = 0, l = expression.length; i < l; i++) {
+      if (this.get('OPERATORS').contains(expression[i])) {
+        if (str.trim().length > 0) {
+          metric = metrics.findProperty('name', str.trim());
+          data.pushObject(Em.Object.create({
+            id: ++id,
+            name: str.trim(),
+            isMetric: true,
+            componentName: metric.component_name,
+            serviceName: metric.service_name
+          }));
+          str = '';
+        }
+        data.pushObject(Em.Object.create({
+          id: ++id,
+          name: expression[i],
+          isOperator: true
+        }));
+      } else {
+        str += expression[i];
+      }
+    }
+    if (str.trim().length > 0) {
+      metric = metrics.findProperty('name', str.trim());
+      data.pushObject(Em.Object.create({
+        id: ++id,
+        name: str.trim(),
+        isMetric: true,
+        componentName: metric.component_name,
+        serviceName: metric.service_name
+      }));
+    }
+    return data;
+  },
+
   next: function () {
     if (!this.get('isSubmitDisabled')) {
       App.router.send('next');

+ 13 - 27
ambari-web/app/controllers/main/service/widgets/create/step3_controller.js

@@ -40,6 +40,11 @@ App.WidgetWizardStep3Controller = Em.Controller.extend({
    */
   widgetName: '',
 
+  /**
+   * @type {string}
+   */
+  widgetDisplayName: '',
+
   /**
    * @type {string}
    */
@@ -81,14 +86,15 @@ App.WidgetWizardStep3Controller = Em.Controller.extend({
     this.set('widgetValues', this.get('content.widgetValues'));
     this.set('widgetMetrics', this.get('content.widgetMetrics'));
     this.set('widgetAuthor', App.router.get('loginName'));
-    this.set('widgetName', '');
-    this.set('widgetDescription', '');
+    this.set('widgetName', this.get('content.widgetDisplayName'));
+    this.set('widgetDisplayName', this.get('content.widgetDisplayName'));
+    this.set('widgetDescription', this.get('content.widgetDescription'));
   },
 
-  //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
+  //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 !this.get('widgetName');
-  }.property('widgetName'),
+    return !this.get('widgetDisplayName');
+  }.property('widgetDisplayName'),
 
   /**
    * collect all needed data to create new widget
@@ -98,7 +104,7 @@ App.WidgetWizardStep3Controller = Em.Controller.extend({
     return {
       WidgetInfo: {
         widget_name: this.get('widgetName'),
-        display_name: this.get('widgetName'),
+        display_name: this.get('widgetDisplayName'),
         widget_type: this.get('content.widgetType'),
         description: this.get('widgetDescription'),
         scope: this.get('widgetScope.name').toUpperCase(),
@@ -115,27 +121,7 @@ App.WidgetWizardStep3Controller = Em.Controller.extend({
     };
   },
 
-  /**
-   * post widget definition to server
-   * @returns {$.ajax}
-   */
-  postWidgetDefinition: function () {
-    return App.ajax.send({
-      name: 'widgets.wizard.add',
-      sender: this,
-      data: {
-        data: this.collectWidgetData()
-      },
-      success: 'postWidgetDefinitionSuccessCallback'
-    });
-  },
-
-  postWidgetDefinitionSuccessCallback: function() {
-
-  },
-
   complete: function () {
-    this.postWidgetDefinition();
-    App.router.send('complete');
+    App.router.send('complete', this.collectWidgetData());
   }
 });

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

@@ -85,6 +85,7 @@ App.WidgetWizardController = App.WizardController.extend({
     dataSets: [],
     templateValue: null,
     widgetName: null,
+    widgetDisplayName: null,
     widgetDescription: null,
     widgetScope: null
   }),
@@ -248,6 +249,27 @@ App.WidgetWizardController = App.WizardController.extend({
     this.save('allMetrics', result);
   },
 
+  /**
+   * post widget definition to server
+   * @returns {$.ajax}
+   */
+  postWidgetDefinition: function (data) {
+    //TODO set correct widget name when adding new one, now widget name the same as to display name
+    data.WidgetInfo.widget_name = data.WidgetInfo.display_name;
+    return App.ajax.send({
+      name: 'widgets.wizard.add',
+      sender: this,
+      data: {
+        data: data
+      },
+      success: 'postWidgetDefinitionSuccessCallback'
+    });
+  },
+
+  postWidgetDefinitionSuccessCallback: function() {
+
+  },
+
   /**
    * Remove all loaded data.
    * Created as copy for App.router.clearAllSteps

+ 175 - 0
ambari-web/app/controllers/main/service/widgets/edit_controller.js

@@ -0,0 +1,175 @@
+/**
+ * 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.WidgetEditController = App.WidgetWizardController.extend({
+
+  name: 'widgetEditController',
+
+  totalSteps: 2,
+
+  content: Em.Object.create({
+    controllerName: 'widgetEditController',
+    widgetService: null,
+    widgetType: '',
+
+    /**
+     * Example:
+     * {
+     *  "display_unit": "%",
+     *  "warning_threshold": 70,
+     *  "error_threshold": 90
+     * }
+     */
+    widgetProperties: {},
+
+    /**
+     * Example:
+     * [{
+     *  widget_id: "metrics/rpc/closeRegion_num_ops",
+     *  name: "rpc.rpc.closeRegion_num_ops",
+     *  pointInTime: true,
+     *  temporal: true,
+     *  category: "default"
+     *  serviceName: "HBASE"
+     *  componentName: "HBASE_CLIENT"
+     *  type: "GANGLIA"//or JMX
+     *  level: "COMPONENT"//or HOSTCOMPONENT
+     * }]
+     * @type {Array}
+     */
+    allMetrics: [],
+
+    /**
+     * Example:
+     * [{
+     *  "name": "regionserver.Server.percentFilesLocal",
+     *  "serviceName": "HBASE",
+     *  "componentName": "HBASE_REGIONSERVER"
+     * }]
+     */
+    widgetMetrics: [],
+
+    /**
+     * Example:
+     * [{
+     *  "name": "Files Local",
+     *  "value": "${regionserver.Server.percentFilesLocal}"
+     * }]
+     */
+    widgetValues: [],
+    expressions: [],
+    dataSets: [],
+    templateValue: null,
+    widgetName: null,
+    widgetDisplayName: null,
+    widgetDescription: null,
+    widgetScope: null,
+    widgetId: null
+  }),
+
+  loadMap: {
+    '1': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.load('widgetType');
+          this.load('widgetProperties');
+          this.load('widgetValues');
+          this.load('widgetMetrics');
+          this.load('expressions');
+          this.load('dataSets');
+          this.load('templateValue');
+        }
+      },
+      {
+        type: 'async',
+        callback: function () {
+          return this.loadAllMetrics();
+        }
+      }
+    ],
+    '2': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.load('widgetName');
+          this.load('widgetDescription');
+          this.load('widgetDisplayName');
+        }
+      }
+    ]
+  },
+
+  /**
+   * 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;
+    }
+  },
+
+  /**
+   * post widget definition to server
+   * @returns {$.ajax}
+   */
+  putWidgetDefinition: function (data) {
+    return App.ajax.send({
+      name: 'widgets.wizard.edit',
+      sender: this,
+      data: {
+        data: data,
+        widgetId: this.get('content.widgetId')
+      },
+      success: 'putWidgetDefinitionSuccessCallback'
+    });
+  },
+
+  putWidgetDefinitionSuccessCallback: function() {
+
+  },
+
+
+  /**
+   * Clear all temporary data
+   */
+  finish: function () {
+    this.setCurrentStep('1', false, true);
+    this.save('widgetType', '');
+    this.save('widgetService', '');
+    this.save('widgetProperties', null);
+    this.save('widgetMetrics', []);
+    this.save('widgetValues', []);
+    this.save('widgetName', '');
+    this.save('widgetDescription', '');
+    this.save('widgetDisplayName', '');
+    this.save('widgetScope', '');
+    this.save('allMetrics', []);
+    this.save('expressions', []);
+    this.save('dataSets', []);
+    this.save('templateValue', '');
+    this.resetDbNamespace();
+  }
+});

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

@@ -2525,6 +2525,8 @@ Em.I18n.translations = {
   'widget.create.wizard.step3.widgetName': 'Widget Name',
   'widget.create.wizard.step3.header': 'Name and Description',
 
+  'widget.edit.wizard.header': 'Edit Widget',
+
   'widget.clone.body': 'Are you sure you want to clone current widget {0}?',
 
   'dashboard.widgets.wizard.step2.addMetrics': 'Add Metrics and operators here...',

+ 1 - 1
ambari-web/app/mixins/common/widget_mixin.js

@@ -392,7 +392,7 @@ App.WidgetMixin = Ember.Mixin.create({
    * make call when clicking on "edit icon" on widget
    */
   editWidget: function (event) {
-
+    this.get('controller').editWidget(this.get('content'));
   }
 
 });

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

@@ -157,6 +157,7 @@ module.exports = App.WizardRoute.extend({
     },
     back: Em.Router.transitionTo('step2'),
     complete: function (router, context) {
+      router.get('widgetWizardController').postWidgetDefinition(context);
       router.get('widgetWizardController.popup').onClose();
     }
   })

+ 113 - 0
ambari-web/app/routes/edit_widget.js

@@ -0,0 +1,113 @@
+/**
+ * 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/edit',
+  enter: function (router, context) {
+    router.get('mainController').dataLoading().done(function () {
+      var widgetEditController = router.get('widgetEditController');
+      var popup = App.ModalPopup.show({
+        classNames: ['full-width-modal'],
+        header: Em.I18n.t('widget.edit.wizard.header'),
+        bodyClass: App.WidgetEditView.extend({
+          controller: widgetEditController
+        }),
+        primary: Em.I18n.t('form.cancel'),
+        showFooter: false,
+        secondary: null,
+
+        onClose: function () {
+          var self = this;
+          widgetEditController.finish();
+          self.hide();
+          var serviceName = widgetEditController.get('content.widgetService');
+          var service = App.Service.find().findProperty('serviceName', serviceName);
+          router.transitionTo('main.services.service', service);
+        },
+
+        didInsertElement: function () {
+          this.fitHeight();
+        }
+
+      });
+      widgetEditController.set('popup', popup);
+      var currentClusterStatus = App.clusterStatus.get('value');
+      if (currentClusterStatus) {
+        if (App.get('testMode')) {
+          widgetEditController.setCurrentStep(App.db.data.WidgetWizard.currentStep);
+        } else {
+          var currStep = App.get('router.widgetEditController.currentStep');
+          widgetEditController.setCurrentStep(currStep);
+        }
+      }
+      Em.run.next(function () {
+        router.transitionTo('step' + widgetEditController.get('currentStep'));
+      });
+    });
+  },
+
+  step1: Em.Route.extend({
+    route: '/step1',
+
+    connectOutlets: function (router) {
+      var controller = router.get('widgetEditController');
+      controller.dataLoading().done(function () {
+        router.get('widgetEditController').setCurrentStep('1');
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('widgetWizardStep2', controller.get('content'));
+      });
+    },
+    unroutePath: function () {
+      return false;
+    },
+
+    next: function (router) {
+      var widgetEditController = router.get('widgetEditController');
+      var widgetStep2controller = router.get('widgetWizardStep2Controller');
+      widgetEditController.save('widgetProperties', widgetStep2controller.get('widgetProperties'));
+      widgetEditController.save('widgetMetrics', widgetStep2controller.get('widgetMetrics'));
+      widgetEditController.save('widgetValues', widgetStep2controller.get('widgetValues'));
+      widgetEditController.save('expressions', widgetStep2controller.get('expressions'));
+      widgetEditController.save('dataSets', widgetStep2controller.get('dataSets'));
+      widgetEditController.save('templateValue', widgetStep2controller.get('templateValue'));
+      router.transitionTo('step2');
+    }
+  }),
+
+  step2: Em.Route.extend({
+    route: '/step2',
+
+    connectOutlets: function (router) {
+      var controller = router.get('widgetEditController');
+      controller.dataLoading().done(function () {
+        router.get('widgetEditController').setCurrentStep('2');
+        controller.loadAllPriorSteps();
+        controller.connectOutlet('widgetWizardStep3', controller.get('content'));
+      });
+    },
+    unroutePath: function () {
+      return false;
+    },
+    back: Em.Router.transitionTo('step1'),
+    complete: function (router, context) {
+      router.get('widgetEditController').putWidgetDefinition(context);
+      router.get('widgetEditController.popup').onClose();
+    }
+  })
+});

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

@@ -571,6 +571,27 @@ module.exports = Em.Route.extend(App.RouterRedirections, {
 
   addWidget: require('routes/add_widget'),
 
+  editServiceWidget: function (router, context) {
+    if (context) {
+      var widgetController = router.get('widgetEditController');
+      widgetController.save('widgetService', context.get('serviceName'));
+      widgetController.save('widgetType', context.get('widgetType'));
+      widgetController.save('widgetProperties', context.get('properties'));
+      widgetController.save('widgetMetrics', context.get('metrics'));
+      widgetController.save('widgetValues', context.get('values'));
+      widgetController.save('widgetName', context.get('widgetName'));
+      widgetController.save('widgetDisplayName', context.get('displayName'));
+      widgetController.save('widgetDescription', context.get('description'));
+      widgetController.save('widgetScope', context.get('scope'));
+      widgetController.save('widgetId', context.get('id'));
+      widgetController.save('allMetrics', []);
+      router.get('widgetWizardStep2Controller').convertData(context, widgetController);
+    }
+    router.transitionTo('editWidget');
+  },
+
+  editWidget: require('routes/edit_widget'),
+
   services: Em.Route.extend({
     route: '/services',
     index: Em.Route.extend({

+ 1 - 0
ambari-web/app/styles/enhanced_service_dashboard.less

@@ -172,6 +172,7 @@
   }
 }
 
+#edit-widget-wizard,
 #add-widget-wizard {
   #add-widget-step1 {
     .widgets-info-container {

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

@@ -22,7 +22,7 @@
   <form>
     <div class="row-fluid">
       <div class="span2 title">{{t widget.create.wizard.step3.widgetName}}</div>
-      <div class="span10">{{view Ember.TextField valueBinding="widgetName"}}</div>
+      <div class="span10">{{view Ember.TextField valueBinding="widgetDisplayName"}}</div>
     </div>
 
     <div class="row-fluid">

+ 55 - 0
ambari-web/app/templates/main/service/widgets/edit.hbs

@@ -0,0 +1,55 @@
+{{!
+* 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" id="edit-widget-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.step2.header}}</a></li>
+              <li {{bindAttr class="isStep2:active view.isStep2Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep2 target="controller"}}>{{t widget.create.wizard.step3.header}}</a></li>
+            </ul>
+          </div>
+          {{#if view.isStep1}}
+            <div class="preview" id="widget-preview">
+              <h5>{{t common.preview}}:</h5>
+              <div class="widget">
+                {{view view.previewWidgetClass controllerBinding="App.router.widgetWizardStep2Controller"}}
+              </div>
+            </div>
+          {{/if}}
+          {{#if view.isStep2}}
+            <div class="preview" id="widget-preview">
+              <h5>{{t common.preview}}:</h5>
+              <div class="widget">
+                {{view view.previewWidgetClass controllerBinding="App.router.widgetWizardStep3Controller"}}
+              </div>
+            </div>
+          {{/if}}
+        </div>
+        <div class="wizard-content well span9">
+          {{outlet}}
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

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

@@ -2486,6 +2486,17 @@ var urls = {
         data: JSON.stringify(data.data)
       };
     }
+  },
+
+  'widgets.wizard.edit': {
+    real: '/clusters/{clusterName}/widgets/{widgetId}',
+    mock: '',
+    'format': function (data) {
+      return {
+        type: 'PUT',
+        data: JSON.stringify(data.data)
+      };
+    }
   }
 };
 /**

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

@@ -318,6 +318,7 @@ 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/service/widgets/edit_view');
 require('views/main/service/widgets/create/expression_view');
 
 require('views/main/views_view');

+ 40 - 0
ambari-web/app/views/main/service/widgets/edit_view.js

@@ -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.
+ */
+
+var App = require('app');
+
+App.WidgetEditView = App.WidgetWizardView.extend({
+
+  templateName: require('templates/main/service/widgets/edit'),
+
+  /**
+   * Widget preview should be shown on 2nd step of wizard
+   * @type {boolean}
+   */
+  isStep1: function () {
+    return this.get('controller.currentStep') == "1";
+  }.property('controller.currentStep'),
+
+  /**
+   * Widget preview should be shown on 3rd step of wizard
+   * @type {boolean}
+   */
+  isStep2: function () {
+    return this.get('controller.currentStep') == "2";
+  }.property('controller.currentStep')
+});