Procházet zdrojové kódy

AMBARI-17640. Storm Ambari View should provide a config page & make calls to Storm Rest API (Sriharsha Chintalapani via srimanth)

Srimanth Gunturi před 9 roky
rodič
revize
3ca6faa870

+ 0 - 100
contrib/views/storm/src/main/java/org/apache/ambari/storm/ProxyServlet.java

@@ -1,100 +0,0 @@
-/**
- * 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.
- */
-
-package org.apache.ambari.view.storm;
-
-import org.apache.ambari.view.ViewContext;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.net.URLDecoder;
-import java.util.*;
-import java.io.*;
-
-/**
- * Simple servlet for proxying requests with doAs impersonation.
- */
-public class ProxyServlet extends HttpServlet {
-
-  private ViewContext viewContext;
-
-  @Override
-  public void init(ServletConfig config) throws ServletException {
-    super.init(config);
-
-    ServletContext context = config.getServletContext();
-    viewContext = (ViewContext) context.getAttribute(ViewContext.CONTEXT_ATTRIBUTE);
-  }
-
-  @Override
-  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
-    InputStream body = null;
-    String urlToRead = URLDecoder.decode(request.getParameter("url"));
-    HashMap<String,String> headersMap = this.getHeaders(request);
-    InputStream resultStream = viewContext.getURLStreamProvider().readAsCurrent(urlToRead, "GET", body, headersMap);
-    this.setResponse(request, response, resultStream);
-  }
-
-  @Override
-  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
-    InputStream stream = request.getInputStream();
-    String urlToRead = URLDecoder.decode(request.getParameter("url"));
-    HashMap<String,String> headersMap = this.getHeaders(request);
-    InputStream resultStream = viewContext.getURLStreamProvider().readAsCurrent(urlToRead, "POST", stream, headersMap);
-    this.setResponse(request, response, resultStream);
-  }
-
-  /**
-   * Get headers from request
-   * @param  request HTTPServletRequest
-   * @return HashMap map containing headers
-   */
-  public HashMap<String,String> getHeaders(HttpServletRequest request){
-    Enumeration headerNames = request.getHeaderNames();
-    HashMap<String,String> map = new HashMap<String, String>();
-    while (headerNames.hasMoreElements()) {
-      String key = (String) headerNames.nextElement();
-      String value = request.getHeader(key);
-      map.put(key, value);
-    }
-    map.put("Content-Type",request.getContentType());
-    return map;
-  }
-
-  /**
-   * Set response to the get/post request
-   * @param request      HttpServletRequest
-   * @param response     HttpServletResponse
-   * @param resultStream InputStream
-   */
-  public void setResponse(HttpServletRequest request, HttpServletResponse response, InputStream resultStream) throws IOException{
-    Scanner scanner = new Scanner(resultStream).useDelimiter("\\A");
-    String result = scanner.hasNext() ? scanner.next() : "";
-    Boolean notFound = result == "" || result.indexOf("\"exception\":\"NotFoundException\"") != -1;
-    response.setContentType(request.getContentType());
-    response.setStatus(notFound ? HttpServletResponse.SC_NOT_FOUND : HttpServletResponse.SC_OK);
-    PrintWriter writer = response.getWriter();
-    writer.print(result);
-  }
-}

+ 0 - 37
contrib/views/storm/src/main/resources/WEB-INF/web.xml

@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1" ?>
-
-<!--
-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. Kerberos, LDAP, Custom. Binary/Htt
--->
-
-<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
-         version="2.4">
-
-  <display-name>Proxy Application</display-name>
-  <description>
-    This is the proxy application.
-  </description>
-  <servlet>
-    <servlet-name>ProxyServlet</servlet-name>
-    <servlet-class>org.apache.ambari.view.storm.ProxyServlet</servlet-class>
-  </servlet>
-  <servlet-mapping>
-    <servlet-name>ProxyServlet</servlet-name>
-    <url-pattern>/proxy</url-pattern>
-  </servlet-mapping>
-</web-app>

+ 372 - 0
contrib/views/storm/src/main/resources/scripts/components/BarChart.jsx

@@ -0,0 +1,372 @@
+/**
+ 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.
+*/
+
+define(['react', 'react-dom', 'd3', 'd3.tip'], function(React, ReactDOM, d3) {
+	'use strict';
+	return React.createClass({
+		displayName: 'BarChart',
+		propTypes: {
+			data: React.PropTypes.array.isRequired,
+			width: React.PropTypes.number,
+			height: React.PropTypes.number,
+		},
+		getInitialState: function(){
+			return null;
+		},
+		componentDidUpdate: function(){
+		},
+		componentDidMount: function(){
+			this.setUpSVG();
+			this.initToolTip();
+			this.setLayout();
+			this.initSets();
+			this.barTypeTransition = this.transitionGrouped;
+			this.hiddenLayers = [];
+			this.drawBars();
+			this.drawXAxis();
+			this.drawYAxis();
+			this.drawTooltip();
+			this.drawLegends();
+		},
+		initSets: function(){
+			this.layers = this.dataMapY(this.props.data);
+			// this.setMax();
+			this.setX();
+			this.setY();
+			this.colorDomain();
+			this.setXAxis();
+			this.setYAxis();
+		},
+		setUpSVG: function(){
+			this.svg = d3.select(ReactDOM.findDOMNode(this))
+				.attr('width', this.props.width+"px")
+				.attr('height', this.props.height+30+"px")
+				// .attr("viewBox", "-46 -5 " + (this.props.width+82) + " " + (this.props.height+28) );
+
+			this.container = this.svg.append("g")
+				.attr('class', 'svg-container')
+				.attr("transform", "translate(40,0)");
+
+			this.tipcontainer = this.svg.append('g').classed('tip-g', true)
+				.attr("transform", "translate(" + 40 + "," + 0 + ")");
+
+			this.tipcontainer.append('g').classed('tipLine-g', true).append('line').classed('tipline', true)
+				.style('stroke', '#aaa')
+				.style('visibility', 'hidden')
+				// .style('shape-rendering', 'crispEdges')
+				.attr('x1', 0).attr('x2', 0).attr('y1', 0).attr('y2', this.props.height);
+		},
+		initToolTip: function() {
+			var self = this;
+			var tip = self.tip = d3.tip()
+				.attr('class', 'd3-tip')
+				.offset([-10, 0])
+				.html(function(d) {
+					return self.toolTipHtml.call(self, d);
+				});
+			this.svg.call(tip);
+			$('#container').append($('body > .d3-tip'));
+		},
+		setMax: function() {
+			this.yGroupMax = d3.max(this.layers, function(layer) {
+				return d3.max(layer, function(d) {
+					return d.y;
+				});
+			});
+			this.yGroupMin = d3.min(this.layers, function(layer) {
+				return d3.min(layer, function(d) {
+					return d.y;
+				});
+			});
+			this.yStackMax = d3.max(this.layers, function(layer) {
+				return d3.max(layer, function(d) {
+					return d.y0 + d.y;
+				});
+			});
+			this.yStackMin = d3.min(this.layers, function(layer) {
+				return d3.min(layer, function(d) {
+					return d3.min([d.y0, d.y]);
+				});
+			});
+		},
+		setX: function() {
+			var self = this;
+			this.x = d3.scale.ordinal()
+				.domain(self.layers[0].map(function(d) {
+					return d.x;
+				}))
+				.rangeRoundBands([0, this.props.width], 0.08);
+		},
+		setY: function() {
+			this.y = d3.scale.linear()
+				.domain([this.yStackMin, this.yStackMax])
+				.range([this.props.height, 0]);
+		},
+		setXAxis: function() {
+			this.xAxis = d3.svg.axis().scale(this.x).orient("bottom");
+		},
+		setYAxis: function() {
+			var formatValue = d3.format('.2s');
+			this.yAxis = d3.svg
+							.axis()
+							.scale(this.y)
+							.orient("left")
+							.tickFormat(function(d){return formatValue(d);});
+		},
+		drawXAxis: function(xAxis, container, height) {
+			var xAxis = xAxis || this.xAxis,
+				container = container || this.container,
+				height = height || this.props.height;
+
+			this.xAxisGrp = container['xAxisEl'] = container.append("g")
+				.attr("class", "x axis")
+				.attr("transform", "translate(0," + height + ")")
+				.call(xAxis);
+		},
+		drawYAxis: function(x) {
+			var yAxis = this.yAxis;
+			this.yAxisGrp = this.container.append("g")
+				.attr("class", "y axis");
+			this.yAxisGrp.ticks = this.yAxisGrp.call(yAxis);
+			this.yAxisGrp.append('text')
+				.text(this.props.yAttr[0].toUpperCase() + this.props.yAttr.substr(1,this.props.yAttr.length)).attr("text-anchor", "end")
+				.attr("y", 6)
+				.attr("dy", ".75em")
+				.attr("transform", "rotate(-90)");
+		},
+		dataMapY: function(data) {
+			var self = this;
+			var keys = d3.keys(data[0]).filter(function(key) {
+				return key !== self.props.xAttr;
+			});
+			var layers = this.stack(keys.map(function(yAttr) {
+				return data.map(function(d) {
+					return {
+						x: d[self.props.xAttr],
+						y: d[yAttr],
+						type: yAttr
+					};
+				});
+			}));
+			var allLayers = layers.allLayers = [];
+			layers.forEach(function(d) {
+				allLayers.push(d);
+			})
+			return layers;
+		},
+		setLayout: function() {
+			var self = this;
+			this.stack = d3.layout.stack();
+		},
+		colorDomain: function() {
+			var self = this;
+			this.color = d3.scale.ordinal()
+    			.range(["#b9cde5", "#1B76BB"]);
+			// this.color = d3.scale.category20c();
+			// this.color.domain(d3.keys(this.props.data[0]).filter(function(key) {
+			// 	return key !== self.props.xAttr;
+			// }));
+		},
+		drawBars: function() {
+			var self = this;
+
+			this.layers_g = this.container.selectAll(".barLayer")
+				.data(this.layers);
+
+			this.layers_g
+				.exit()
+				.remove()
+
+			this.layers_g
+				.enter().append("g")
+				.attr("class", "barLayer")
+				.style("fill", function(d, i) {
+					return self.color(d[0].type);
+				});
+
+			this.rect = this.layers_g.selectAll("rect")
+				.data(function(d) {
+					return d;
+				});
+
+			this.rect
+				.exit()
+				.remove()
+
+			this.rect
+				.enter().append("rect")
+				.attr("x", function(d) {
+					return self.x(d.x);
+				})
+				.attr("y", function(d) {
+					return self.props.height;
+				})
+				.attr("width", function(d) {
+					return self.x.rangeBand();
+				})
+				.classed("visible", true)
+				.attr("height", function(d) {
+					return 0;
+				});
+
+			this.barTypeTransition();
+		},
+		transitionGrouped: function() {
+			var x = this.x,
+				y = this.y,
+				height = this.props.height,
+				n = this.layers.length;
+			this.setMax();
+			var yMin = this.yGroupMin < 0 ? this.yGroupMin : 0;
+			this.y.domain([yMin, this.yGroupMax]);
+
+			var barWidth = (x.rangeBand() / n > 25) ? 25 : x.rangeBand() / n;
+			var xArr = new Array(n)
+			this.layers_g.selectAll('rect.visible')
+				.attr("x", function(d, i, j) {
+					if (xArr[i] == undefined) {
+						xArr[i] = x(d.x) + (x.rangeBand() / 2) - (n / 2 * barWidth)
+					} else {
+						xArr[i] += barWidth;
+					}
+					return xArr[i];
+				})
+				.attr("width", barWidth)
+				.transition().duration(500)
+				.attr("y", function(d) {
+					var _y = y(d.y);
+					if (d.y < 0)
+						_y = y(d.y) - (height - y(0));
+					return _y;
+				})
+				.attr("height", function(d) {
+					return (height - y(Math.abs(d.y))) - (height - y(0));
+				});
+			this.container.select(".y.axis").transition().duration(500).call(this.yAxis);
+		},
+		transitionStacked: function() {
+			this.stack(this.layers);
+			var x = this.x,
+				y = this.y,
+				height = this.props.height,
+				self = this,
+				n = this.layers.length;
+			this.setMax();
+			this.y.domain([this.yStackMin, this.yStackMax]);
+
+			var barWidth = (x.rangeBand() / n > 25) ? 25 : x.rangeBand() / n;
+			var xArr = new Array(n);
+			this.layers_g.selectAll('rect.visible').transition().duration(500)
+				.attr("y", function(d) {
+					var _y = y(d.y0 + d.y);
+					if (d.y < 0)
+						_y = y(d.y) - Math.abs(y(d.y0) - y(d.y0 + d.y));
+					return _y;
+				})
+				.attr("height", function(d) {
+					return Math.abs(y(d.y0) - y(d.y0 + d.y));
+				})
+				.attr("x", function(d, i, j) {
+					xArr[i] = x(d.x) + (x.rangeBand() / 2) - (barWidth / 2)
+					return xArr[i];
+				})
+				.attr("width", barWidth);
+			this.container.select(".y.axis").transition().duration(500).call(this.yAxis);
+		},
+		drawTooltip: function() {
+			var self = this;
+			var x = this.x.rangeBand ? this.x : d3.scale.ordinal()
+				.domain(self.data.map(function(d) {
+					return d[self.props.xAttr];
+				}))
+				.rangeRoundBands([0, this.props.width]);
+
+			var tipline = this.tipcontainer.select('.tipline');
+
+			this.tipcontainer.append('g').classed('tipRect-g', true).selectAll(".tipRect")
+				.data(this.props.data)
+				.enter().append("rect")
+				.attr("class", "tipRect")
+				.style('opacity', '0')
+				.attr("x", function(d) {
+					return self.x(d[self.props.xAttr]);
+				})
+				.attr("width", function() {
+					return x.rangeBand();
+				})
+				.attr("y", function(d) {
+					return 0;
+				})
+				.attr("height", function(d) {
+					return self.props.height;
+				})
+				.on('mouseover', function(d) {
+					var x1 = parseInt(d3.select(this).attr('x')) + parseInt((x.rangeBand() / 2));
+					tipline.attr('x1', x1).attr('x2', x1);
+					tipline.style('visibility', 'visible');
+					return self.tip.show(d);
+				})
+				.on('mouseout', function(d) {
+					tipline.style('visibility', 'hidden');
+					return self.tip.hide(d)
+				});
+		},
+		toolTipHtml: function(d) {
+			var self = this;
+			var html = d[self.props.xAttr] + '<table><tbody>';
+			_.each(d, function(val, key) {
+				if (key != self.props.xAttr)
+					html += '<tr><td>' + key + ' </td><td> ' + val + '</td></tr>';
+			});
+			html += '</tbody></table>';
+			return html;
+		},
+		drawLegends: function() {
+			var self = this;
+			var legends = this.legendsEl = document.createElement('ul');
+			legends = d3.select(legends)
+				.attr('class', 'legends')
+				.style('list-style', 'none')
+
+			var legend = legends.selectAll('.legend')
+				.data(this.color.domain())
+				.enter()
+				.append('li')
+				.attr('class', 'legend')
+
+			legend.append('div')
+				.style('width', '10px')
+				.style('height', '10px')
+				.style('display', 'inline-block')
+				.style('background-color', function(d) {
+					return self.color(d);
+				});
+
+			legend.append('span')
+				.style('padding', '4px 0 4px 4px')
+				.text(function(d) {
+					return d;
+				});
+		},
+		render: function() {
+			return (
+				<svg></svg>
+			);
+		},
+	});
+});

+ 85 - 0
contrib/views/storm/src/main/resources/scripts/components/SearchLogs.jsx

@@ -0,0 +1,85 @@
+/**
+ 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.
+*/
+
+define(['react',
+ 'react-dom'],
+ function(React, ReactDOM) {
+	'use strict';
+    return React.createClass({
+		displayName: 'SearchBar',
+		getInitialState: function() {
+			return null;
+		},
+		render: function() {			
+			return (
+				<div className="col-md-3 pull-right searchbar">
+                    <div className="input-group">
+                        <input type="text" id="searchBox" className="form-control" placeholder="Search in Logs"/>
+                        <div className="input-group-btn">
+                            <div className="btn-group" role="group">
+                                <div className="dropdown dropdown-lg">
+                                    <button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
+                                        <span className="caret"></span>
+                                    </button>
+                                    <div className="dropdown-menu dropdown-menu-right" role="menu">
+                                        <form>
+                                            <div>
+                                                <label><input type="checkbox" id="searchArchivedLogs"/> Search archived logs</label>
+                                            </div>
+                                            <div>
+                                                <label><input type="checkbox" id="deepSearch"/> Deep Search</label>
+                                            </div>
+                                        </form>
+                                    </div>
+                                </div>
+                                <button type="button" className="btn btn-default" onClick={this.handleSearch}>
+                                    <i className="fa fa-search"></i>
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+			);
+    	},
+        handleSearch: function(){
+            var searchBoxEl = document.getElementById('searchBox');
+            var searchArchivedLogsEl = document.getElementById('searchArchivedLogs');
+            var deepSearchEl = document.getElementById('deepSearch');
+
+            var url = App.baseURL+'/';
+            if(deepSearchEl.checked == true){
+                url += "deep_search_result.html";
+            }else{
+                url += "search_result.html";
+            }
+            url += '?search='+searchBoxEl.value+'&id='+ this.props.id +'&count=1';
+            if(searchArchivedLogsEl.checked == true){
+                if(deepSearchEl.checked == true){
+                    url += "&search-archived=on";
+                }else{
+                    url += "&searchArchived=checked";
+                }
+            }
+            window.open(url, '_blank');
+
+            searchBoxEl.value = '';
+            searchArchivedLogsEl.checked = false;
+            deepSearchEl.checked = false;
+        },
+    }); 
+});

+ 0 - 139
contrib/views/storm/src/main/resources/scripts/components/SpoutGraph.jsx

@@ -1,139 +0,0 @@
-/**
- 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.
-*/
-
-define(['react',
-	'react-dom'
-	], function(React, ReactDOM){
-	'use strict';
-	return React.createClass({
-		displayName: 'SpoutGraph',
-		propTypes: {
-			spout: React.PropTypes.object.isRequired
-		},
-		getInitialState: function(){
-			this.syncData();
-			this.fields = ['', 'Acked', 'Failed', 'Emitted', 'Transferred'];
-			this.colors = ['#0000b4','#0082ca','#0094ff','#0d4bcf'];
-			this.grid = d3.range(10).map(function(i){
-				return {'x1':0,'y1':0,'x2':0,'y2':240};
-			});
-
-			this.tickVals = this.grid.map(function(d,i){
-				if(i>0){ return i*10; }
-				else if(i===0){ return "100";}
-			});
-
-			this.xscale = d3.scale.linear()
-						.domain([10,250])
-						.range([0,350]);
-
-			this.yscale = d3.scale.linear()
-							.domain([0,this.fields.length])
-							.range([0,600]);
-
-			this.colorScale = d3.scale.quantize()
-							.domain([0,this.fields.length])
-							.range(this.colors);
-			return null;
-		},
-		syncData: function(){
-			this.values = [this.props.spout.acked, this.props.spout.failed,
-							this.props.spout.emitted, this.props.spout.transferred];
-			console.log(this.values);
-		},
-		componentDidMount: function(){
-			this.renderGraph();
-		},
-		componentWillUpdate: function(){
-			this.syncData();
-		},
-		componentDidUpdate: function(){
-
-		},
-		renderGraph: function(){
-			this.canvas = d3.select(ReactDOM.findDOMNode(this))
-							.attr({'width':640,'height':400});
-
-			this.grids = this.canvas.append('g')
-							  .attr('transform','translate(150,10)')
-							  .selectAll('line')
-							  .data(this.grid)
-							  .enter()
-							  .append('line')
-							  .attr({'x1':function(d,i){ return i*30; },
-									 'y1':function(d){ return d.y1; },
-									 'x2':function(d,i){ return i*30; },
-									 'y2':function(d){ return d.y2; },
-								})
-							  .style({'stroke':'#adadad','stroke-width':'1px'});
-			var	xAxis = d3.svg.axis();
-				xAxis
-					.orient('bottom')
-					.scale(this.xscale)
-					.tickValues(this.tickVals);
-
-			var	yAxis = d3.svg.axis();
-				yAxis
-					.orient('left')
-					.scale(this.yscale)
-					.tickSize(2)
-					.tickFormat(function(d,i){ return this.fields[i]; }.bind(this))
-					.tickValues(d3.range(this.fields.length));
-
-			var y_xis = this.canvas.append('g')
-							  .attr("transform", "translate(150,0)")
-							  .call(yAxis);
-
-			var x_xis = this.canvas.append('g')
-							  .attr("transform", "translate(150,480)")
-							  .call(xAxis);
-
-			var chart = this.canvas.append('g')
-								.attr("transform", "translate(150,0)")
-								.attr('class','bars')
-								.selectAll('rect')
-								.data(this.values)
-								.enter()
-								.append('rect')
-								.attr('height',19)
-								.attr({'x':0,'y':function(d,i){ return this.yscale(i)+19; }.bind(this)})
-								.style('fill',function(d,i){ return this.colorScale(i); }.bind(this))
-								.attr('width',function(d){ return 0; });
-
-
-			var transit = d3.select("svg").selectAll("rect")
-							    .data(this.values)
-							    .transition()
-							    .duration(1000) 
-							    .attr("width", function(d) {return this.xscale(d); }.bind(this));
-
-			var transitext = this.canvas.select('.bars')
-								.selectAll('text')
-								.data(this.values)
-								.enter()
-								.append('text')
-								.attr({'x':function(d) {return this.xscale(d)-200; }.bind(this),'y':function(d,i){ return this.yscale(i)+35; }.bind(this)})
-								.text(function(d){ return d; }).style({'fill':'#fff','font-size':'14px'});
-		},
-		render: function(){
-			return (
-				<svg className="spout-chart"></svg>
-			);
-		}
-	});
-});

+ 6 - 6
contrib/views/storm/src/main/resources/scripts/components/TopologyGraph.jsx

@@ -166,8 +166,8 @@ define(['react', 'react-dom', 'd3', 'd3.tip'], function(React, ReactDOM, d3) {
 						return "images/icon-bolt.png";
 					}
 				})
-				.attr("width", "68px")
-				.attr("height", "68px")
+				.attr("width", "30px")
+				.attr("height", "30px")
                 .on('mouseover', function(d) {
                     this.tip.show(d);
                 }.bind(this))
@@ -177,7 +177,7 @@ define(['react', 'react-dom', 'd3', 'd3.tip'], function(React, ReactDOM, d3) {
 
             g.append("svg:text")
                 .attr("dx", 18)
-                .attr("dy", 78)
+                .attr("dy", 38)
                 .text(function(d) {
                     return d.id; });
 
@@ -193,12 +193,12 @@ define(['react', 'react-dom', 'd3', 'd3.tip'], function(React, ReactDOM, d3) {
                     dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
                     normX = deltaX / dist,
                     normY = deltaY / dist,
-                    sourcePadding = 68,
+                    sourcePadding = 30,
                     targetPadding = 5,
                     sourceX = d.source.x + (sourcePadding * normX),
-                    sourceY = d.source.y + (sourcePadding * normY) + 34,
+                    sourceY = d.source.y + (sourcePadding * normY) + 15,
                     targetX = d.target.x - (targetPadding * normX),
-                    targetY = d.target.y - (targetPadding * normY) + 34;
+                    targetY = d.target.y - (targetPadding * normY) + 15;
                 return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
             });
             

+ 6 - 0
contrib/views/storm/src/main/resources/scripts/models/VTopology.js

@@ -79,6 +79,12 @@ define(['require',
     profileHeap: function(options){
       return this.constructor.nonCrudOperation.call(this, Globals.baseURL + '/api/v1/topology/' + options.id  + '/profiling/dumpheap/' + options.hostPort, 'GET', options);
     },
+    getTopologyLag: function(options){
+      return this.constructor.nonCrudOperation.call(this, Globals.baseURL + '/api/v1/topology/' + options.id  + '/lag', 'GET', options);
+    },
+    getWorkerHost: function(options){
+      return this.constructor.nonCrudOperation.call(this, Globals.baseURL + '/api/v1/topology-workers/' + options.id, 'GET', options);
+    },
   }, {});
   return VTopology;
 });

+ 25 - 3
contrib/views/storm/src/main/resources/scripts/router/Router.js

@@ -38,11 +38,15 @@ define([
 		},
 		initialize: function() {
 			App.baseURL = Utils.getStormHostDetails();
-			// App.baseURL = 'http://c6502:8744';
 			this.showRegions();
+			this.listenTo(this, "route", this.postRouteExecute, this);
 		},
 		showRegions: function() {
 			this.renderFooter();
+			if(window != window.parent){
+				var viewPath = this.getParameterByName("viewpath");
+				location.hash = viewPath ? viewPath : '';
+			}
 		},
 		renderFooter: function(){
 			require(['jsx!views/Footer'], function(Footer){
@@ -55,7 +59,25 @@ define([
 			this.postRouteExecute();
 		},
 		preRouteExecute: function() {},
-		postRouteExecute: function(name, args) {},
+		postRouteExecute: function(name, args) {
+			this.shareUrl();
+		},
+
+		getParameterByName: function(name) {
+			name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+			var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+				results = regex.exec(location.search);
+			return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+		},
+
+		shareUrl : function(){
+			if(window != window.parent){
+				var parentWindow = window.parent;
+				var parentHash = parentWindow.location.hash.split("?")[0];
+				var newurl = parentWindow.location.protocol + "//" + parentWindow.location.host + parentHash + '?viewpath='+encodeURIComponent(location.hash);
+				parentWindow.history.replaceState({path:newurl},'',newurl);
+			}
+		},
 
 		/**
 		 * Define route handlers here
@@ -98,4 +120,4 @@ define([
 
 	return AppRouter;
 
-});
+});

+ 0 - 11
contrib/views/storm/src/main/resources/scripts/utils/Overrides.js

@@ -19,15 +19,4 @@
 define(['backbone'], function(Backbone){
 	'use strict';
 	
-	// $('.loader').hide();
-	
-	Backbone.ajax = function() {
-		if(!arguments[0].data){
-			var urlPart = arguments[0].url.split('url=')[0];
-		    var stormUrlPart = arguments[0].url.split('url=')[1];
-		    urlPart += 'url=' + encodeURIComponent(stormUrlPart);
-		    arguments[0].url = urlPart;
-		}
-	    return Backbone.$.ajax.apply(Backbone.$, arguments);
-	};
 });

+ 12 - 46
contrib/views/storm/src/main/resources/scripts/utils/Utils.js

@@ -26,59 +26,25 @@ define(['require',
     var Utils = {};
 
     Utils.getStormHostDetails = function() {
-        var url = location.pathname + 'proxy?url=';
+        var url;
+        var urlParts = location.pathname.split('/');
+        var apiUrl = '/api/v1/'+urlParts[1]+'/'+urlParts[2]+'/versions/'+urlParts[3]+'/instances/'+urlParts[4];
         $.ajax({
-            url: '/api/v1/clusters/',
+            url: apiUrl,
             cache: false,
             type: 'GET',
             async: false,
-            success: function(response) {
-                var result = JSON.parse(response);
-                if (_.isArray(result.items) && result.items.length) {
-                    var flag = false;
-                    _.each(result.items, function(object) {
-                        if (!flag) {
-                            $.ajax({
-                                url: object.href,
-                                type: 'GET',
-                                async: false,
-                                success: function(res) {
-                                    var config = JSON.parse(res);
-                                    var hostname;
-                                    _.each(config.alerts, function(obj) {
-                                        if (obj.Alert.service_name === "STORM" && obj.Alert.definition_name === "storm_webui") {
-                                            hostname = obj.Alert.host_name;
-                                        }
-                                    });
-                                    if (_.isUndefined(hostname) || hostname == "") {
-                                        console.log("Error detected while fetching storm hostname and port number");
-                                    } else {
-                                        var obj = _.findWhere(config.service_config_versions, { "service_name": "STORM" });
-                                        if (!_.isUndefined(obj)) {
-                                            var stormConfig = _.findWhere(obj.configurations, { "type": "storm-site" });
-                                            if (!_.isUndefined(stormConfig)) {
-                                                flag = true;
-                                                url += 'http://' + hostname + ':' + stormConfig.properties['ui.port'];
-                                            } else {
-                                                console.log("Error detected while fetching storm hostname and port number");
-                                            }
-                                        } else {
-                                            console.log("Error detected while fetching storm hostname and port number");
-                                        }
-                                    }
-                                },
-                                error: function(res) {
-                                    console.log("Error detected while fetching storm hostname and port number");
-                                }
-                            });
-                        }
-                    });
+            dataType: 'json',
+            success: function(response){
+                var props = response.ViewInstanceInfo.properties;
+                if(props['storm.host'] && props['storm.port']){
+                    url = "http://"+props['storm.host']+":"+props['storm.port'];
                 } else {
-                    console.log("Currently, no service is configured in ambari");
+                    Utils.notifyError("Failed to get storm hostname and port.");
                 }
             },
-            error: function(error) {
-                console.log("Error detected while fetching storm hostname and port number");
+            error: function(error){
+                Utils.notifyError("Failed to get storm hostname and port.");
             }
         });
         return url;

+ 4 - 2
contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx

@@ -24,12 +24,13 @@ define([
 	'collections/BaseCollection',
 	'models/VTopology',
 	'jsx!components/Breadcrumbs',
+	'jsx!components/SearchLogs',
 	'jsx!views/ProfilingView',
 	'utils/Utils',
 	'bootbox',
 	'bootstrap',
 	'bootstrap-switch'
-	],function(Table, Pagination, React, ReactDOM, BaseCollection, VTopology, Breadcrumbs, ProfilingView, Utils, bootbox){
+	],function(Table, Pagination, React, ReactDOM, BaseCollection, VTopology, Breadcrumbs, SearchLogs, ProfilingView, Utils, bootbox){
 	'use strict';
 
 	return React.createClass({
@@ -341,7 +342,7 @@ define([
 						model: React.PropTypes.object.isRequired
 					},
 					render: function(){
-						if(this.props.model.get('errorTime') != 0) {
+						if(this.props.model.get('errorTime') && this.props.model.get('errorTime') != 0) {
 							var d = new Date(this.props.model.get('errorTime') * 1000),
 							date = d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
 							return (<span>{date}</span>);
@@ -374,6 +375,7 @@ define([
 			return (
 				<div>					
 					<Breadcrumbs links={this.getLinks()} />
+					<SearchLogs id={this.state.componentObj.topologyId}/>
 					<div className="row">
 						<div className="col-sm-12">
 							<div className="box filter">

+ 191 - 16
contrib/views/storm/src/main/resources/scripts/views/TopologyDetailView.jsx

@@ -28,12 +28,14 @@ define([
 	'jsx!containers/TopologyConfiguration',
 	'jsx!containers/TopologyDetailGraph',
 	'jsx!components/Breadcrumbs',
+	'jsx!components/SearchLogs',
+	'jsx!components/BarChart',
 	'jsx!views/RebalanceView',
 	'bootbox',
 	'x-editable',
 	'bootstrap',
 	'bootstrap-switch'
-	],function(Table, Pagination, Utils, React, ReactDOM, BaseCollection, VTopology, BaseModel, TopologyConfiguration, TopologyDetailGraph, Breadcrumbs, RebalanceView, bootbox, XEditable){
+	],function(Table, Pagination, Utils, React, ReactDOM, BaseCollection, VTopology, BaseModel, TopologyConfiguration, TopologyDetailGraph, Breadcrumbs, SearchLogs, BarChart, RebalanceView, bootbox, XEditable){
 	'use strict';
 
 	return React.createClass({
@@ -45,6 +47,7 @@ define([
 			this.model = new VTopology({'id': this.props.id});
 			this.spoutCollection = new BaseCollection();
 			this.boltCollection = new BaseCollection();
+			this.lagCollection = new BaseCollection();
 			this.systemFlag = false;
 			this.windowSize = ':all-time';
 			this.initializeData();
@@ -52,7 +55,10 @@ define([
 				model: this.model,
 				graphData: {},
 				logLevels: {},
-				rebalanceModalOpen: false
+				rebalanceModalOpen: false,
+				lagData: [],
+				hideKafkaLagBox: false,
+				workerHostPort: ''
 			};
 		},
 		componentWillMount: function(){
@@ -73,6 +79,12 @@ define([
 					this.debugAction(state);
 				}.bind(this)
 			});
+			$("#lag-graph").hide();
+			$("#kafkaSpout").bootstrapSwitch({
+		    	onSwitchChange: function() {
+		    		$('#lag-graph, #lag-table').slideToggle();
+		    	}
+		    });
 			$('[data-rel="tooltip"]').tooltip();
 			$('.loader').hide();
 		},
@@ -107,6 +119,9 @@ define([
 			if(this.state.rebalanceModalOpen){
 				$('#modal-rebalance').modal("show");
 			}
+			if(this.refs.barChart){
+				ReactDOM.findDOMNode(document.getElementById('lag-graph')).appendChild(this.refs.barChart.legendsEl)
+			}
 			$('.loader').hide();
 		},
 		initializeData: function(){
@@ -128,6 +143,8 @@ define([
 			});
 			this.initializeGraphData();
 			this.initializeLogConfig();
+			this.initializeLagData();
+			this.initializeWorkerData();
 		},
 		initializeGraphData: function(){
 			this.model.getGraphData({
@@ -165,6 +182,63 @@ define([
 				}
 			});
 		},
+
+		initializeLagData: function(){
+			this.model.getTopologyLag({
+				id: this.model.get('id'),
+				success: function(model, response){
+					if(response.error){
+						Utils.notifyError(response.error);
+					} else {
+						if(model && model.length){
+							var result = JSON.parse(model[0].spoutLagResult);
+							for(var i = 0; i < result.length; i++){
+								result[i]['spoutId'] = model[0].spoutId;
+								result[i]['spoutType'] = model[0].spoutType;
+							}
+							this.resetLagCollection(result);
+						} else {
+							this.setState({hideKafkaLagBox : true});
+						}
+					}
+				}.bind(this)
+			})
+		},
+		initializeWorkerData: function(){
+			this.model.getWorkerHost({
+				id: this.model.get('id'),
+				success: function(model, response){
+					if(response.error){
+						Utils.notifyError(response.error);
+					} else {
+						var workerHostPortArr = model.hostPortList;
+						var result = '';
+						for(var i = 0; i < workerHostPortArr.length; i++){
+							result += workerHostPortArr[i].host+':'+workerHostPortArr[i].port
+							if(i !== workerHostPortArr.length - 1){
+								result += ', \n';
+							}
+						}
+						this.setState({'workerHostPort': result})
+					}
+				}.bind(this)
+			})
+		},
+		resetLagCollection: function(model){
+			this.lagCollection.reset(model);
+			this.setState({"lagData": model});
+		},
+		getLagColums: function(){
+			var self = this;
+			return [
+				{name: 'spoutId', title: 'Id', tooltip:'Id'},
+				{name: 'topic', title: 'Topic', tooltip:'Topic'},
+				{name: 'partition', title: 'Partition', tooltip:'Partition'},
+				{name: 'logHeadOffset', title: 'Latest Offset', tooltip:'Latest Offset'},
+				{name: 'consumerCommittedOffset', title: 'Spout Committed Offset', tooltip:'Spout Committed Offset'},
+				{name: 'lag', title: 'Lag', tooltip:'Lag'},
+			];
+		},
 		resetLogCollection: function(model) {
 			this.collection.reset();
 			this.setState({logLevels: model.namedLoggerLevels});
@@ -262,7 +336,7 @@ define([
 						model: React.PropTypes.object.isRequired
 					},
 					render: function(){
-						if(this.props.model.get('errorTime') != 0) {
+						if(this.props.model.get('errorTime') && this.props.model.get('errorTime') != 0) {
 							var d = new Date(this.props.model.get('errorTime') * 1000),
 							date = d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
 							return (<span>{date}</span>);
@@ -323,7 +397,7 @@ define([
 						model: React.PropTypes.object.isRequired
 					},
 					render: function(){
-						if(this.props.model.get('errorTime') != 0) {
+						if(this.props.model.get('errorTime') && this.props.model.get('errorTime') != 0) {
 							var d = new Date(this.props.model.get('errorTime') * 1000),
 							date = d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
 							return (<span>{date}</span>);
@@ -574,6 +648,7 @@ define([
 			return (
 				<div>
 					<Breadcrumbs links={this.getLinks()} />
+					<SearchLogs id={this.model.get('id')}/>
 					<div className="row">
 						<div className="col-sm-12">
 							<div className="box filter">
@@ -626,22 +701,69 @@ define([
 						</div>
 					</div>
 					<div className="row">
-						<div className="col-sm-4">
+						<div className="col-sm-5">
 							<div className="summary-tile">
 								<div className="summary-title">Topology Summary</div>
-								<div className="summary-body">
-									<p><strong>ID: </strong>{this.state.model.get('id')}</p>
-									<p><strong>Owner: </strong>{this.state.model.get('owner')}</p>
-									<p><strong>Status: </strong>{this.state.model.get('status')}</p>
-									<p><strong>Uptime: </strong>{this.state.model.get('uptime')}</p>
-									<p><strong>Workers: </strong>{this.state.model.get('workersTotal')}</p>
-									<p><strong>Executors: </strong>{this.state.model.get('executorsTotal')}</p>
-									<p><strong>Tasks: </strong>{this.state.model.get('tasksTotal')}</p>
-									<p><strong>Memory: </strong>{this.state.model.get('assignedTotalMem')}</p>
+								<div className="summary-body form-horizontal">
+									<div className="form-group">
+										<label className="col-sm-4 control-label">ID:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('id')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Owner:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('owner')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Status:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('status')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Uptime:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('uptime')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Workers:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('workersTotal')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Executors:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('executorsTotal')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Tasks:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('tasksTotal')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Memory:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static">{this.state.model.get('assignedTotalMem')}</p>
+										</div>
+									</div>
+									<div className="form-group">
+										<label className="col-sm-4 control-label">Worker-Host:Port:</label>
+										<div className="col-sm-8">
+										<p className="form-control-static preformatted">{this.state.workerHostPort}</p>
+										</div>
+									</div>
+
 								</div>
 							</div>
 						</div>
-						<div className="col-sm-8">
+						<div className="col-sm-7">
 							<div className="stats-tile">
 								<div className="stats-title">Topology Stats</div>
 								<div className="stats-body">
@@ -669,6 +791,59 @@ define([
 							<TopologyDetailGraph model={this.state.model} graphData={this.state.graphData}/>
 						</div>
 					</div>
+					{this.state.hideKafkaLagBox ? null : 
+						<div className="row">
+							<div className="col-sm-12">
+								<div className="box">
+									<div className="box-header">
+										<h4>Kafka Spout Lag</h4>
+										<div className="box-control">
+											<input 
+												id="kafkaSpout" 
+												type="checkbox" 
+												data-size="mini" 
+												data-off-color="success" 
+												data-off-text="Table" 
+												data-on-color="info" 
+												data-on-text="Graph" />
+										</div>
+									</div>
+									<div className="box-body">
+										<div className="row">
+											<div className="col-sm-12">
+												<div id="lag-graph" className="displayNone">
+													{this.lagCollection.length > 0 ? 
+													<BarChart
+														ref="barChart"
+														width={window != window.parent ? 1100 : 1300}
+														height={400}
+														xAttr="spoutId-partition"
+														yAttr="count"
+														data={this.lagCollection.toJSON().map(function(d){
+															return {
+																'Latest Offset': d.logHeadOffset,
+																'Spout Committed Offset': d.consumerCommittedOffset,
+																'spoutId-partition': d.spoutId+'-'+d.partition
+															};
+														})}
+													/>
+													: null}
+												</div>
+												<div id="lag-table">
+													<Table 
+														className="table table-striped table-bordered" 
+														collection={this.lagCollection} 
+														emptyText="No Data Found." 
+														columns={this.getLagColums()} 
+													/>
+												</div>
+											</div>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>
+					}
 					<div className="row">
 						<div className="col-sm-12">
 							{this.spouts}
@@ -842,4 +1017,4 @@ define([
     		}
 	    },
 	});
-});
+});

+ 64 - 2
contrib/views/storm/src/main/resources/styles/style.css

@@ -135,6 +135,10 @@ body {
     border-left-color: #1bbb60;
 }
 
+.preformatted {
+  white-space: pre;
+}
+
 /* Boxes */
 .box {
     position: relative;
@@ -168,6 +172,9 @@ body {
 .box .box-header .box-control {
     float: right;
 }
+.box .box-header .box-control .bootstrap-switch {
+  margin: 9px 2px;
+}
 .box .box-header .box-control a {
     display: inline-block;
     width: 20px;
@@ -317,10 +324,13 @@ body {
 }
 .summary-tile .summary-body strong {
   display: inline-block;
-  width: 100px;
+  width: 120px;
   margin-right: 10px;
   text-align: right;
 }
+.summary-tile .summary-body .form-group {
+  margin-bottom: 0px;
+}
 
 .stats-tile {
   display: block;
@@ -393,7 +403,7 @@ REACT
     z-index: 99;
     line-height: 1;
     font-weight: bold;
-    padding: 5px;
+    padding: 10px;
     background: rgba(0, 0, 0, 0.8);
     color: #fff;
     border-radius: 2px;
@@ -461,6 +471,34 @@ text.id {
 marker {
   fill: grey;
 }
+.axis path, .axis rect {
+    fill: none;
+    stroke: grey;
+    stroke-width: 1;
+    shape-rendering: crispEdges;
+}
+
+.axis line {
+    stroke: grey;
+    stroke-width: 1;
+    shape-rendering: crispEdges;
+}
+
+.d3-tip table {
+  margin-top: 8px;
+  width : 100%;
+}
+.d3-tip table tr td {
+  padding: 3px;
+  border: 1px red solid;
+}
+ul.legends {
+  text-align: center;
+}
+ul.legends li.legend{
+  display: inline-block;
+  margin-right: 10px;
+}
 .table-summary {
     font-size: 12px;
     font-weight: 700;
@@ -494,4 +532,28 @@ marker {
   right: 0;
   background: url('../images/loader.gif') rgba(255,255,255,0.75) no-repeat center center;
   z-index: 9;
+}
+.searchbar{
+  margin-top: 15px;
+}
+.searchbar .btn-group{
+  display: flex !important;
+}
+.searchbar .dropdown-toggle{
+  border-radius: 0;
+  margin-left: -1px;
+}
+.searchbar .btn-group .btn {
+    margin-left: -1px;
+}
+.searchbar .dropdown-menu{
+  padding: 10px 15px 5px;
+}
+.searchbar .dropdown-menu input[type="checkbox"]{
+  vertical-align: top;
+  margin-right: 5px;
+}
+.searchbar .open > .dropdown-toggle.btn-default:hover, 
+.searchbar .open > .dropdown-toggle.btn-default:focus{
+  border: 1px solid transparent;
 }

+ 15 - 10
contrib/views/storm/src/main/resources/view.xml

@@ -20,13 +20,18 @@ limitations under the License. Kerberos, LDAP, Custom. Binary/Htt
   <version>0.1.0</version>
   <build>${env.BUILD_NUMBER}</build>
   <description>Ambari view for Apache Storm</description>
-  <auto-instance>
-    <name>STORM_CLUSTER_INSTANCE</name>
-    <label>Storm View</label>
-    <description>Storm View (auto-created)</description>
-    <stack-id>HDP-2.*</stack-id>
-    <services>
-      <service>STORM</service>
-    </services>
-  </auto-instance>
-</view>
+  <parameter>
+	<name>storm.host</name>
+	<description>Enter the Storm host name for accessing Storm. Host must be accessible from Ambari Server.</description>
+	<label>Storm Hostname</label>
+	<placeholder>storm-host.example.com</placeholder>
+	<required>true</required>
+  </parameter>
+  <parameter>
+	<name>storm.port</name>
+	<description>Enter the Storm port for accessing Storm.</description>
+	<label>Storm Port</label>
+	<placeholder>8744</placeholder>
+	<required>true</required>
+  </parameter>
+</view>