You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2017/10/20 21:01:03 UTC
[06/22] ambari git commit: Revert "AMBARI-21955. Update React version
to 15.6.2 to get MIT license. (Sanket Shah via yusaku)"
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx b/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx
new file mode 100644
index 0000000..5847ef9
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx
@@ -0,0 +1,534 @@
+/**
+ 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([
+ 'jsx!components/Table',
+ 'jsx!modules/Table/Pagination',
+ 'react',
+ 'react-dom',
+ '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, SearchLogs, ProfilingView, Utils, bootbox){
+ 'use strict';
+
+ return React.createClass({
+ displayName: 'ComponentDetailView',
+ propTypes: {
+ id: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired
+ },
+ getInitialState: function(){
+ this.model = new VTopology({'id': this.props.id});
+ this.systemFlag = (this.props.name.startsWith('__')) ? true : false;
+ this.windowSize = ':all-time';
+ this.initializeData();
+ return {
+ componentObj: {},
+ profilingModalOpen: false
+ };
+ },
+ componentWillMount: function(){
+ $('.loader').show();
+ },
+ componentWillUpdate: function(){
+ $('.loader').show();
+ $('#collapse-input').off('hidden.bs.collapse').off('shown.bs.collapse');
+ $('#collapse-output').off('hidden.bs.collapse').off('shown.bs.collapse');
+ $('#collapse-executor').off('hidden.bs.collapse').off('shown.bs.collapse');
+ $('#collapse-error').off('hidden.bs.collapse').off('shown.bs.collapse');
+ },
+ componentDidMount: function(){
+ $(".boot-switch.systemSum").bootstrapSwitch({
+ size: 'small',
+ onSwitchChange: function(event, state){
+ this.systemFlag = state;
+ this.initializeData();
+ }.bind(this)
+ });
+
+ $(".boot-switch.debug").bootstrapSwitch({
+ size: 'small',
+ onSwitchChange: function(event, state){
+ this.debugAction(state);
+ }.bind(this)
+ });
+ $('.loader').hide();
+ },
+ componentDidUpdate: function(){
+ $('#collapse-input').on('hidden.bs.collapse', function () {
+ $("#input-box").toggleClass("fa-compress fa-expand");
+ }).on('shown.bs.collapse', function() {
+ $("#input-box").toggleClass("fa-compress fa-expand");
+ });
+
+ $('#collapse-output').on('hidden.bs.collapse', function () {
+ $("#output-box").toggleClass("fa-compress fa-expand");
+ }).on('shown.bs.collapse', function() {
+ $("#output-box").toggleClass("fa-compress fa-expand");
+ });
+
+ $('#collapse-executor').on('hidden.bs.collapse', function () {
+ $("#executor-box").toggleClass("fa-compress fa-expand");
+ }).on('shown.bs.collapse', function() {
+ $("#executor-box").toggleClass("fa-compress fa-expand");
+ });
+
+ $('#collapse-error').on('hidden.bs.collapse', function () {
+ $("#error-box").toggleClass("fa-compress fa-expand");
+ }).on('shown.bs.collapse', function() {
+ $("#error-box").toggleClass("fa-compress fa-expand");
+ });
+ $('#modal-profiling').on('hidden.bs.modal', function (e) {
+ this.initializeData();
+ this.setState({"profilingModalOpen":false});
+ }.bind(this));
+ if(this.state.profilingModalOpen){
+ $('#modal-profiling').modal("show");
+ }
+ $('.loader').hide();
+ },
+ initializeData: function(){
+ this.model.getComponent({
+ id: this.props.id,
+ name: this.props.name,
+ window: this.windowSize,
+ sys: this.systemFlag,
+ success: function(model, response){
+ if(response.error || model.error){
+ Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')');
+ } else {
+ this.setState({"componentObj": model});
+ }
+ }.bind(this),
+ error: function(model, response, options){
+ Utils.notifyError("Error occured in fetching topology component data.");
+ }
+ });
+ },
+ renderWindowOptions: function(){
+ var arr = this.state.componentObj.spoutSummary || this.state.componentObj.boltStats;
+ if(arr){
+ return arr.map(function(object, i){
+ return ( <option key={i} value={object.window}>{object.windowPretty}</option> );
+ });
+ } else {
+ return null;
+ }
+ },
+ handleWindowChange: function(e){
+ this.windowSize = e.currentTarget.value;
+ this.initializeData();
+ },
+ getLinks: function() {
+ var links = [
+ {link: '#!/dashboard', title: 'Dashboard'},
+ {link: '#!/topology', title: 'Topology Listing'},
+ {link: '#!/topology/'+this.state.componentObj.topologyId, title: this.state.componentObj.name || ""},
+ {link: 'javascript:void(0);', title: this.state.componentObj.id || ""}
+ ];
+ return links;
+ },
+ renderStatsRow: function(){
+ var spoutFlag = (this.state.componentObj.componentType === 'spout' ? true: false);
+ var statsArr = this.state.componentObj.spoutSummary || this.state.componentObj.boltStats;
+ if(statsArr){
+ return statsArr.map(function(stats, i){
+ return (
+ <tr key={i}>
+ <td>{stats.windowPretty}</td>
+ <td>{stats.emitted}</td>
+ <td>{stats.transferred}</td>
+ {spoutFlag ? <td>{stats.completeLatency}</td> : null}
+ {!spoutFlag ? <td>{stats.executeLatency}</td> : null}
+ {!spoutFlag ? <td>{stats.executed}</td> : null}
+ {!spoutFlag ? <td>{stats.processLatency}</td> : null}
+ <td>{stats.acked}</td>
+ <td>{stats.failed}</td>
+ </tr>
+ );
+ });
+ }
+ },
+ renderAccordion: function(type, header, searchField, searchCb, collection, emptyText, columns, toggleCb){
+ return (
+ <div className="box">
+ <div className="box-header" data-toggle="collapse" data-target={"#collapse-"+type} aria-expanded="false" aria-controls={"collapse-"+type}>
+ <h4>{header} ( {this.state.componentObj.windowHint} )</h4>
+ <h4 className="box-control">
+ <a href="javascript:void(0);" className="primary">
+ <i className="fa fa-compress" id={type+"-box"} onClick={toggleCb}></i>
+ </a>
+ </h4>
+ </div>
+ <div className="box-body collapse in" id={"collapse-"+type}>
+ <div className="input-group col-sm-4">
+ <input type="text" onKeyUp={searchCb} className="form-control" placeholder={"Search by "+searchField} />
+ <span className="input-group-btn">
+ <button className="btn btn-primary" type="button"><i className="fa fa-search"></i></button>
+ </span>
+ </div>
+ <Table className="table table-striped" collection={collection} emptyText={emptyText} columns={columns()} />
+ {type === 'error' ? <Pagination collection={collection} /> : null}
+ </div>
+ </div>
+ );
+ },
+ renderInputStats: function(){
+ var inputCollection = Utils.ArrayToCollection(this.state.componentObj.inputStats, new BaseCollection());
+ inputCollection.searchFields = ['component'];
+ var searchCb = function(e){
+ var value = e.currentTarget.value;
+ inputCollection.search(value);
+ };
+ var toggleCb = function(e){
+ $("#collapse-input").collapse('toggle');
+ }
+ return this.renderAccordion('input', 'Input Stats', 'component', searchCb, inputCollection, 'No input stats found !', this.getInputColumns, toggleCb);
+ },
+ getInputColumns: function(){
+ return [
+ {name: 'component', title: 'Component', tooltip: 'The ID assigned to a the Component by the Topology.'},
+ {name: 'stream', title: 'Stream', tooltip: 'The name of the Tuple stream given in the Topolgy, or "default" if none was given.'},
+ {name: 'executeLatency', title: 'Execute Latency (ms)', tooltip: 'The average time a Tuple spends in the execute method. The execute method may complete without sending an Ack for the tuple.'},
+ {name: 'executed', title: 'Executed', tooltip: 'The number of incoming Tuples processed.'},
+ {name: 'processLatency', title: 'Process Latency (ms)', tooltip: 'The average time it takes to Ack a Tuple after it is first received. Bolts that join, aggregate or batch may not Ack a tuple until a number of other Tuples have been received.'},
+ {name: 'acked', title: 'Acked', tooltip: 'The number of Tuples acknowledged by this Bolt.'},
+ {name: 'failed', title: 'Failed', tooltip: 'The number of tuples Failed by this Bolt.'}
+ ];
+ },
+ renderOutputStats: function(){
+ var outputCollection = Utils.ArrayToCollection(this.state.componentObj.outputStats, new BaseCollection());
+ outputCollection.searchFields = ['stream'];
+ var searchCb = function(e){
+ var value = e.currentTarget.value;
+ outputCollection.search(value);
+ };
+ var toggleCb = function(e){
+ $("#collapse-output").collapse('toggle');
+ }
+ return this.renderAccordion('output', 'Output Stats', 'stream', searchCb, outputCollection, 'No output stats found !', this.getOutputColumns, toggleCb);
+ },
+ getOutputColumns: function(){
+ if(this.state.componentObj.componentType === 'spout'){
+ return [
+ {name: 'stream', title: 'Stream', tooltip: 'The name of the Tuple stream given in the Topolgy, or "default" if none was given.'},
+ {name: 'emitted', title: 'Emitted', tooltip: 'The number of Tuples emitted.'},
+ {name: 'transferred', title: 'Transferred', tooltip: 'The number of Tuples emitted that sent to one or more bolts.'},
+ {name: 'completeLatency', title: 'Complete Latency (ms)', tooltip: 'The average time a Tuple "tree" takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.'},
+ {name: 'acked', title: 'Acked', tooltip: 'The number of Tuple "trees" successfully processed. A value of 0 is expected if no acking is done.'},
+ {name: 'failed', title: 'Failed', tooltip: 'The number of Tuple "trees" that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.'}
+ ];
+ } else {
+ return [
+ {name: 'stream', title: 'Stream', tooltip: 'The name of the Tuple stream given in the Topolgy, or "default" if none was given.'},
+ {name: 'emitted', title: 'Emitted', tooltip: 'The number of Tuples emitted.'},
+ {name: 'transferred', title: 'Transferred', tooltip: 'The number of Tuples emitted that sent to one or more bolts.'}
+ ];
+ }
+ },
+ renderExecutorStats: function(){
+ var executorCollection = Utils.ArrayToCollection(this.state.componentObj.executorStats, new BaseCollection());
+ executorCollection.searchFields = ['id'];
+ var searchCb = function(e){
+ var value = e.currentTarget.value;
+ executorCollection.search(value);
+ };
+ var toggleCb = function(e){
+ $("#collapse-executor").collapse('toggle');
+ }
+ return this.renderAccordion('executor', 'Executor Stats', 'id', searchCb, executorCollection, 'No executor stats found !', this.getExecutorColumns, toggleCb);
+ },
+ getExecutorColumns: function(){
+ var self = this;
+ if(this.state.componentObj.componentType === 'spout'){
+ return [
+ {name: 'id', title: 'Id', tooltip: 'The unique executor ID.'},
+ {name: 'uptime', title: 'Uptime', tooltip: 'The length of time an Executor (thread) has been alive.'},
+ {name: 'port', title: 'Host:Port', tooltip: 'The hostname reported by the remote host. (Note that this hostname is not the result of a reverse lookup at the Nimbus node.) Click on it to open the logviewer page for this Worker.', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ return ( <a href={this.props.model.get('workerLogLink')} target="_blank"> {this.props.model.get('host')}:{this.props.model.get('port')} </a>);
+ }
+ })},
+ {name: 'emitted', title: 'Emitted', tooltip: 'The number of Tuples emitted.'},
+ {name: 'transferred', title: 'Transferred', tooltip: 'The number of Tuples emitted that sent to one or more bolts.'},
+ {name: 'completeLatency', title: 'Complete Latency (ms)', tooltip: 'The average time a Tuple "tree" takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.'},
+ {name: 'acked', title: 'Acked', tooltip: 'The number of Tuple "trees" successfully processed. A value of 0 is expected if no acking is done.'},
+ {name: 'failed', title: 'Failed', tooltip: 'The number of Tuple "trees" that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.'},
+ {name: 'workerLogLink', title: 'Dumps', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ var link = this.props.model.get('workerLogLink');
+ link = ""+link.split('/log')[0]+"/dumps/"+self.props.id+"/"+this.props.model.get('host')+":"+this.props.model.get('port');
+ return (<a href={link} className="btn btn-primary btn-xs" target="_blank"><i className="fa fa-file-text"></i></a>);
+ }
+ })}
+ ];
+ } else {
+ return [
+ {name: 'id', title: 'Id', tooltip: 'The unique executor ID.'},
+ {name: 'uptime', title: 'Uptime', tooltip: 'The length of time an Executor (thread) has been alive.'},
+ {name: 'port', title: 'Host:Port', tooltip: 'The hostname reported by the remote host. (Note that this hostname is not the result of a reverse lookup at the Nimbus node.) Click on it to open the logviewer page for this Worker.', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ return ( <a href={this.props.model.get('workerLogLink')} target="_blank"> {this.props.model.get('host')}:{this.props.model.get('port')} </a>);
+ }
+ })},
+ {name: 'emitted', title: 'Emitted', tooltip: 'The number of Tuples emitted.'},
+ {name: 'transferred', title: 'Transferred', tooltip: 'The number of Tuples emitted that sent to one or more bolts.'},
+ {name: 'capacity', title: 'Capacity (last 10m)', tooltip: "If this is around 1.0, the corresponding Bolt is running as fast as it can, so you may want to increase the Bolt's parallelism. This is (number executed * average execute latency) / measurement time."},
+ {name: 'executeLatency', title: 'Execute Latency (ms)', tooltip: 'The average time a Tuple spends in the execute method. The execute method may complete without sending an Ack for the tuple.'},
+ {name: 'executed', title: 'Executed', tooltip: 'The number of incoming Tuples processed.'},
+ {name: 'processLatency', title: 'Process Latency (ms)', tooltip: 'The average time it takes to Ack a Tuple after it is first received. Bolts that join, aggregate or batch may not Ack a tuple until a number of other Tuples have been received.'},
+ {name: 'acked', title: 'Acked', tooltip: 'The number of Tuples acknowledged by this Bolt.'},
+ {name: 'failed', title: 'Failed', tooltip: 'The number of tuples Failed by this Bolt.'},
+ {name: 'workerLogLink', title: 'Dumps', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ var link = this.props.model.get('workerLogLink');
+ link = ""+link.split('/log')[0]+"/dumps/"+self.props.id+"/"+this.props.model.get('host')+":"+this.props.model.get('port');
+ return (<a href={link} className="btn btn-primary btn-xs" target="_blank"><i className="fa fa-file-text"></i></a>);
+ }
+ })}
+ ];
+ }
+ },
+ renderErrorStats: function(){
+ var errorCollection = Utils.ArrayToCollection(this.state.componentObj.componentErrors, new BaseCollection());
+ errorCollection.searchFields = ['error'];
+ var searchCb = function(e){
+ var value = e.currentTarget.value;
+ errorCollection.search(value);
+ };
+ var toggleCb = function(e){
+ $("#collapse-error").collapse('toggle');
+ }
+ return this.renderAccordion('error', 'Error Stats', 'error', searchCb, errorCollection, 'No errors found !', this.getErrorColumns, toggleCb);
+ },
+ getErrorColumns: function(){
+ return [
+ {name: 'errorTime', title: 'Time', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ 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>);
+ } else return (<span></span>);
+ }
+ })},
+ {name: 'errorPort', title: 'Host:Port', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ return ( <a href={this.props.model.get('errorWorkerLogLink')} target="_blank"> {this.props.model.get('errorHost')}:{this.props.model.get('errorPort')} </a>);
+ }
+ })},
+ {name: 'error', title: 'Error'}
+ ];
+ },
+ render: function() {
+ if(this.state.componentObj.debug){
+ $(".boot-switch.debug").bootstrapSwitch('state', true, true);
+ } else {
+ $(".boot-switch.debug").bootstrapSwitch('state', false, true);
+ }
+ if(this.systemFlag){
+ $(".boot-switch.systemSum").bootstrapSwitch('state', true, true);
+ } else {
+ $(".boot-switch.systemSum").bootstrapSwitch('state', false, true);
+ }
+ var spoutFlag = (this.state.componentObj.componentType === 'spout' ? true: false);
+ return (
+ <div>
+ <Breadcrumbs links={this.getLinks()} />
+ <SearchLogs id={this.state.componentObj.topologyId}/>
+ <div className="row">
+ <div className="col-sm-12">
+ <div className="box filter">
+ <div className="box-body form-horizontal">
+ <div className="form-group no-margin">
+ <label className="col-sm-1 control-label">Window</label>
+ <div className="col-sm-2">
+ <select className="form-control" onChange={this.handleWindowChange} value={this.windowSize}>
+ {this.renderWindowOptions()}
+ </select>
+ </div>
+ <label className="col-sm-2 control-label">System Summary</label>
+ <div className="col-sm-2">
+ <input className="boot-switch systemSum" type="checkbox" />
+ </div>
+ <label className="col-sm-1 control-label">Debug</label>
+ <div className="col-sm-1">
+ <input className="boot-switch debug" type="checkbox"/>
+ </div>
+ <div className="col-sm-3 text-right">
+ <div className="btn-group" role="group">
+ <button type="button" className="btn btn-primary" onClick={this.handleProfiling} title="Profiling & Debugging" data-rel="tooltip">
+ <i className="fa fa-cogs"></i>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="row">
+ <div className="col-sm-4">
+ <div className="summary-tile">
+ <div className="summary-title">Component Summary</div>
+ <div className="summary-body">
+ <p><strong>ID: </strong>{this.state.componentObj.id}</p>
+ <p><strong>Topology: </strong>{this.state.componentObj.name}</p>
+ <p><strong>Executors: </strong>{this.state.componentObj.executors}</p>
+ <p><strong>Tasks: </strong>{this.state.componentObj.tasks}</p>
+ <p><strong>Debug: </strong><a href={this.state.componentObj.eventLogLink} target="_blank">events</a></p>
+ </div>
+ </div>
+ </div>
+ <div className="col-sm-8">
+ <div className="stats-tile">
+ <div className="stats-title">{spoutFlag ? "Spout Stats" : "Bolt Stats"}</div>
+ <div className="stats-body">
+ <table className="table table-condensed no-margin">
+ <thead>
+ <tr>
+ <th><span data-rel="tooltip" title="The past period of time for which the statistics apply.">Window</span></th>
+ <th><span data-rel="tooltip" title="The number of Tuples emitted.">Emitted</span></th>
+ <th><span data-rel="tooltip" title="The number of Tuples emitted that sent to one or more bolts.">Transferred</span></th>
+ {spoutFlag ? <th><span data-rel="tooltip" title='The average time a Tuple "tree" takes to be completely processed by the Topology. A value of 0 is expected if no acking is done.'>Complete Latency (ms)</span></th> : null}
+ {!spoutFlag ? <th><span data-rel="tooltip" title="The average time a Tuple spends in the execute method. The execute method may complete without sending an Ack for the tuple.">Execute Latency (ms)</span></th> : null}
+ {!spoutFlag ? <th><span data-rel="tooltip" title="The number of incoming Tuples processed.">Executed</span></th> : null}
+ {!spoutFlag ? <th><span data-rel="tooltip" title="The average time it takes to Ack a Tuple after it is first received. Bolts that join, aggregate or batch may not Ack a tuple until a number of other Tuples have been received.">Process Latency (ms)</span></th> : null}
+ <th><span data-rel="tooltip" title={spoutFlag ? 'The number of Tuple "trees" successfully processed. A value of 0 is expected if no acking is done.' : "The number of Tuples acknowledged by this Bolt."}>Acked</span></th>
+ <th><span data-rel="tooltip" title={spoutFlag ? 'The number of Tuple "trees" that were explicitly failed or timed out before acking was completed. A value of 0 is expected if no acking is done.' : "The number of tuples Failed by this Bolt."}>Failed</span></th>
+ </tr>
+ </thead>
+ <tbody>
+ {this.renderStatsRow()}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="row">
+ <div className="col-sm-12">
+ {this.state.componentObj.inputStats ? this.renderInputStats() : null}
+ {this.state.componentObj.outputStats ? this.renderOutputStats() : null}
+ {this.state.componentObj.executorStats ? this.renderExecutorStats() : null}
+ {this.state.componentObj.componentErrors ? this.renderErrorStats() : null}
+ </div>
+ </div>
+ {this.state.profilingModalOpen ? <ProfilingView modalId="modal-profiling" topologyId={this.props.id} executorStats={this.state.componentObj.executorStats} /> : null}
+ </div>
+ );
+ },
+ handleProfiling: function(){
+ this.setState({"profilingModalOpen":true});
+ },
+ debugAction: function(toEnableFlag){
+ if(toEnableFlag){
+ bootbox.prompt({
+ title: 'Do you really want to debug this component ? If yes, please, specify sampling percentage.',
+ value: this.state.componentObj.samplingPct ? this.state.componentObj.samplingPct : "10",
+ buttons: {
+ confirm: {
+ label: 'Yes',
+ className: "btn-success",
+ },
+ cancel: {
+ label: 'No',
+ className: "btn-default",
+ }
+ },
+ callback: function(result) {
+ if(result == null) {
+ $(".boot-switch.debug").bootstrapSwitch('toggleState', true);
+ } else if(result == "" || isNaN(result) || result < 0) {
+ Utils.notifyError("Enter valid sampling percentage");
+ $(".boot-switch.debug").bootstrapSwitch('toggleState', true)
+ } else {
+ this.model.debugComponent({
+ id: this.state.componentObj.topologyId,
+ name: this.state.componentObj.id,
+ debugType: 'enable',
+ percent: result,
+ success: function(model, response){
+ if(response.error || model.error){
+ Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')');
+ } else {
+ this.initializeData();
+ Utils.notifySuccess("Debugging enabled successfully.");
+ }
+ }.bind(this),
+ error: function(model, response, options){
+ Utils.notifyError("Error occured in enabling debugging.");
+ }
+ });
+ }
+ }.bind(this)
+ });
+ } else {
+ var title = "Do you really want to stop debugging this component ?";
+ var successCb = function(){
+ this.model.debugComponent({
+ id: this.state.componentObj.topologyId,
+ name: this.state.componentObj.id,
+ debugType: 'disable',
+ percent: '0',
+ success: function(model, response){
+ if(response.error || model.error){
+ Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')');
+ } else {
+ this.initializeData();
+ Utils.notifySuccess("Debugging disabled successfully.");
+ }
+ }.bind(this),
+ error: function(model, response, options){
+ Utils.notifyError("Error occured in disabling debugging.");
+ }
+ });
+ }.bind(this);
+ var cancelCb = function(){
+ $(".boot-switch.debug").bootstrapSwitch('toggleState', true)
+ }.bind(this);
+ Utils.ConfirmDialog(' ', title, successCb, cancelCb);
+ }
+ },
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/Dashboard.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/Dashboard.jsx b/contrib/views/storm/src/main/resources/scripts/views/Dashboard.jsx
new file mode 100644
index 0000000..3f4f682
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/Dashboard.jsx
@@ -0,0 +1,65 @@
+/**
+ 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([
+ 'jsx!components/Table',
+ 'jsx!components/RadialChart',
+ 'react',
+ 'react-dom',
+ 'jsx!containers/ClusterSummary',
+ 'jsx!containers/NimbusSummary',
+ 'jsx!containers/SupervisorSummary',
+ 'jsx!containers/TopologyListing',
+ 'jsx!containers/NimbusConfigSummary'
+ ],function(Table,RadialChart, React, ReactDOM, ClusterSummary, NimbusSummary, SupervisorSummary, TopologyListing, NimbusConfigSummary){
+ 'use strict';
+
+ return React.createClass({
+ displayName: 'Dashboard',
+ getInitialState: function(){
+ return null;
+ },
+ componentWillMount: function(){
+ $('.loader').show();
+ },
+ componentDidMount: function(){
+ $('.loader').hide();
+ },
+ componentWillUpdate: function(){
+ $('.loader').show();
+ },
+ componentDidUpdate: function(){
+ $('.loader').hide();
+ },
+ render: function() {
+ return (
+ <div>
+ <div className="row" style={{marginTop: '20px'}}>
+ <ClusterSummary />
+ <TopologyListing fromDashboard={true} />
+ </div>
+ <div className="row">
+ <div className="col-sm-12">
+ <NimbusConfigSummary />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx b/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx
new file mode 100644
index 0000000..98e63e9
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx
@@ -0,0 +1,48 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+*
+ http://www.apache.org/licenses/LICENSE-2.0
+*
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+define(['react', 'react-dom', 'models/VCluster', 'utils/Utils'], function(React, ReactDOM, VCluster, Utils) {
+ 'use strict';
+ return React.createClass({
+ displayName: 'Footer',
+ getInitialState: function(){
+ this.initializeData();
+ return {
+ version: 0
+ };
+ },
+ initializeData: function(){
+ this.model = new VCluster();
+ this.model.fetch({
+ success: function(model, response){
+ if(response.error || model.error){
+ Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')');
+ } else {
+ this.setState({version: model.get('stormVersion')});
+ }
+ }.bind(this),
+ error: function(model, response, options){
+ Utils.notifyError("Error occured in fetching cluster summary data.");
+ }
+ });
+ },
+ render: function() {
+ return (<p className="text-center">Apache Storm - v{this.state.version}</p>);
+ }
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/NimbusSummaryView.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/NimbusSummaryView.jsx b/contrib/views/storm/src/main/resources/scripts/views/NimbusSummaryView.jsx
new file mode 100644
index 0000000..6221d3f
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/NimbusSummaryView.jsx
@@ -0,0 +1,65 @@
+/**
+ 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([
+ 'jsx!components/Table',
+ 'react',
+ 'react-dom',
+ 'jsx!containers/NimbusSummary',
+ 'jsx!components/Breadcrumbs'
+ ],function(Table, React, ReactDOM, NimbusSummary, Breadcrumbs){
+ 'use strict';
+
+ return React.createClass({
+ displayName: 'NimbusSummaryView',
+ getInitialState: function(){
+ return null;
+ },
+ componentWillMount: function(){
+ $('.loader').show();
+ },
+ componentDidMount: function(){
+ $('.loader').hide();
+ },
+ componentWillUpdate: function(){
+ $('.loader').show();
+ },
+ componentDidUpdate: function(){
+ $('.loader').hide();
+ },
+ render: function() {
+ return (
+ <div>
+ <Breadcrumbs links={this.getLinks()} />
+ <div className="row">
+ <div className="col-sm-12">
+ <NimbusSummary/>
+ </div>
+ </div>
+ </div>
+ );
+ },
+ getLinks: function() {
+ var links = [
+ {link: '#!/dashboard', title: 'Dashboard'},
+ {link: 'javascript:void(0);', title: 'Nimbus Summary'}
+ ];
+ return links;
+ }
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/ProfilingView.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/ProfilingView.jsx b/contrib/views/storm/src/main/resources/scripts/views/ProfilingView.jsx
new file mode 100644
index 0000000..f5ffefe
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/ProfilingView.jsx
@@ -0,0 +1,214 @@
+/**
+ 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',
+ 'collections/BaseCollection',
+ 'models/VTopology',
+ 'utils/Utils',
+ 'utils/Globals',
+ 'jsx!components/Table',
+ 'bootstrap'
+ ], function(React, ReactDOM, BaseCollection, VTopology, Utils, Globals, Table) {
+ 'use strict';
+ return React.createClass({
+ displayName: 'Profiling',
+ propTypes: {
+ modalId: React.PropTypes.string.isRequired,
+ topologyId: React.PropTypes.string.isRequired,
+ executorStats: React.PropTypes.array.isRequired
+ },
+ getInitialState: function(){
+ this.model = new VTopology();
+ this.selectedWorker = [];
+ return null;
+ },
+ componentWillMount: function(){
+ this.syncData();
+ },
+ componentDidMount: function(){
+ $('.error-msg').hide();
+ $('.warning-msg').hide();
+ $('.success-msg').hide();
+ },
+ componentDidUpdate: function(){
+
+ },
+ syncData: function(){
+ this.collection = new BaseCollection();
+ if(this.props.executorStats.length){
+ var data = {};
+ this.props.executorStats.map(function(obj){
+ var hostPort = obj.host + ":" + obj.port;
+ if(!data[hostPort]){
+ data[hostPort] = {};
+ }
+ if(!data[hostPort].idArr){
+ data[hostPort].idArr = [];
+ }
+ data[hostPort].idArr.push(obj.id);
+ });
+ var keys = this.hostPortArr = _.keys(data);
+ keys.map(function(key){
+ this.collection.add(new Backbone.Model({
+ hostPort: key,
+ executorId: data[key].idArr
+ }));
+ }.bind(this));
+ }
+ },
+ handleJStackOp: function(){
+ this.performOp('JStack');
+ },
+ handleRestartWorker: function(){
+ this.performOp('RestartWorker');
+ },
+ handleHeapOp: function(){
+ this.performOp('Heap');
+ },
+ performOp: function(opType){
+ if(!this.selectedWorker.length){
+ $('.warning-msg').show();
+ $('.success-msg').hide();
+ $('.error-msg').hide();
+ } else {
+ $('.warning-msg').hide();
+ $('.success-msg').hide();
+ $('.error-msg').hide();
+ var promiseArr = [];
+ this.selectedWorker.map(function(worker){
+ var obj = {
+ id: this.props.topologyId,
+ hostPort: worker
+ };
+ if(opType === 'JStack'){
+ promiseArr.push(this.model.profileJStack(obj));
+ } else if(opType === 'RestartWorker'){
+ promiseArr.push(this.model.profileRestartWorker(obj));
+ } else if(opType === 'Heap'){
+ promiseArr.push(this.model.profileHeap(obj));
+ }
+ }.bind(this));
+ Promise.all(promiseArr)
+ .then(function(resultsArr){
+ $('.success-msg').show();
+ })
+ .catch(function(){
+ $('.error-msg').show();
+ });
+ }
+ },
+ getColumns: function(){
+ var self = this;
+ return [
+ {
+ name: 'hostPort',
+ title: React.createClass({
+ handleChange: function(e){
+ if($(e.currentTarget).prop('checked')){
+ self.selectedWorker = self.hostPortArr;
+ $('[name="single"]').prop("checked", true)
+ } else {
+ self.selectedWorker = [];
+ $('[name="single"]').prop("checked", false)
+ }
+ },
+ render: function(){
+ return (
+ <input type="checkbox" name="selectAll" onChange={this.handleChange}/>
+ );
+ }
+ }),
+ component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ handleChange: function(e){
+ var hostPort = this.props.model.get('hostPort')
+ if($(e.currentTarget).prop('checked')){
+ self.selectedWorker.push(hostPort);
+ } else {
+ var index = _.indexOf(self.selectedWorker, hostPort);
+ if(index > -1){
+ self.selectedWorker.splice(index, 1);
+ }
+ }
+ },
+ render: function(){
+ return (
+ <input type="checkbox" name="single" onChange={this.handleChange}/>
+ );
+ }
+ })
+ },
+ {name: 'hostPort', title:'Host:Port'},
+ {name: 'executorId', title:'Executor Id', component: React.createClass({
+ propTypes: {
+ model: React.PropTypes.object.isRequired
+ },
+ render: function(){
+ var executors = this.props.model.get('executorId').join(', ');
+ return (
+ <span>{executors}</span>
+ );
+ }
+ })}
+ ];
+ },
+ closeModal: function(){
+ $('#'+this.props.modalId).modal("hide");
+ },
+ render: function() {
+ return (
+ <div className="modal fade" id={this.props.modalId} role="dialog">
+ <div className="modal-dialog">
+ <div className="modal-content">
+ <div className="modal-header">
+ <button type="button" className="close" data-dismiss="modal">×</button>
+ <h4 className="modal-title">Profiling & Debugging</h4>
+ </div>
+ <div className="modal-body">
+ <div className="alert alert-warning alert-dismissible warning-msg" role="alert">
+ <strong>Warning!</strong> Please select atleast one worker to perform operation.
+ </div>
+ <div className="alert alert-success alert-dismissible success-msg" role="alert">
+ <strong>Success!</strong> Action performed successfully.
+ </div>
+ <div className="alert alert-danger alert-dismissible error-msg" role="alert">
+ <strong>Error!</strong> Error occured while performing the action.
+ </div>
+ <div className="clearfix">
+ <div className="btn-group btn-group-sm pull-right">
+ <button type="button" className="btn btn-primary" onClick={this.handleJStackOp}>JStack</button>
+ <button type="button" className="btn btn-primary" onClick={this.handleRestartWorker}>Restart Worker</button>
+ <button type="button" className="btn btn-primary" onClick={this.handleHeapOp}>Heap</button>
+ </div>
+ </div>
+ <hr />
+ <Table className="table table-bordered" collection={this.collection} columns={this.getColumns()} emptyText="No workers found !" />
+ </div>
+ <div className="modal-footer">
+ <button type="button" className="btn btn-default" onClick={this.closeModal}>Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ },
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/RebalanceView.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/RebalanceView.jsx b/contrib/views/storm/src/main/resources/scripts/views/RebalanceView.jsx
new file mode 100644
index 0000000..33f5963
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/RebalanceView.jsx
@@ -0,0 +1,223 @@
+/**
+ 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',
+ 'utils/Utils',
+ 'models/VCluster',
+ 'utils/Globals',
+ 'bootstrap',
+ 'bootstrap-slider'], function(React, ReactDOM, Utils, VCluster, Globals) {
+ 'use strict';
+ return React.createClass({
+ displayName: 'Rebalance',
+ propTypes: {
+ modalId: React.PropTypes.string.isRequired,
+ topologyId: React.PropTypes.string.isRequired,
+ topologyExecutors: React.PropTypes.string.isRequired,
+ spouts: React.PropTypes.array.isRequired,
+ bolts: React.PropTypes.array.isRequired
+ },
+ getInitialState: function(){
+ var spoutArr = [];
+ var boltArr = [];
+ this.getClusterDetails();
+ return {
+ spout: spoutArr,
+ bolt: boltArr,
+ workers: parseInt(this.props.topologyExecutors,10),
+ waitTime: 30,
+ freeSlots: 0
+ };
+ },
+ componentWillMount: function(){
+ this.syncData();
+ },
+ componentDidMount: function(){
+ $('.error-msg').hide();
+ },
+ componentDidUpdate: function(){
+ $('#ex1').slider({
+ value: this.state.workers,
+ min: 0,
+ step: 1,
+ max: this.state.workers + this.state.freeSlots,
+ tooltip_position: 'bottom',
+ formatter: function(value) {
+ return 'Current value: ' + value;
+ }
+ });
+ },
+ syncData: function(){
+ var spoutArr, boltArr;
+ if(this.props.spouts){
+ spoutArr = this.props.spouts.map(function(spout){
+ var obj = {
+ key: spout.spoutId,
+ value: spout.executors
+ };
+ return obj;
+ });
+ this.setState({'spout': spoutArr});
+ }
+ if(this.props.bolts){
+ boltArr = this.props.bolts.map(function(bolt){
+ var obj = {
+ key: bolt.boltId,
+ value: bolt.executors
+ };
+ return obj;
+ });
+ this.setState({'bolt': boltArr});
+ }
+ },
+ getClusterDetails: function(){
+ var model = new VCluster();
+ model.fetch({
+ success: function(model){
+ this.setState({"freeSlots": model.get('slotsFree')});
+ }.bind(this)
+ });
+ },
+ rebalanceTopologyAction: function(e){
+ var arr = $('form').serializeArray();
+ var errorFlag = false;
+ var finalData = {
+ "rebalanceOptions": {
+ "executors": {}
+ },
+ };
+ var waitTime;
+ var result = arr.map(function(obj){
+ if(!errorFlag){
+ if(obj.value === ''){
+ errorFlag = true;
+ } else {
+ if(obj.name === 'workers'){
+ finalData.rebalanceOptions.numWorkers = obj.value;
+ } else if(obj.name === 'waitTime'){
+ waitTime = obj.value;
+ } else {
+ finalData.rebalanceOptions.executors[obj.name] = obj.value;
+ }
+ }
+ }
+ });
+ if(errorFlag){
+ $('.error-msg').show();
+ } else {
+ $('.error-msg').hide();
+ $.ajax({
+ url: Globals.baseURL + '/api/v1/topology/' + this.props.topologyId + '/rebalance/' + waitTime,
+ data: (_.keys(finalData.rebalanceOptions).length) ? JSON.stringify(finalData) : null,
+ cache: false,
+ contentType: 'application/json',
+ type: 'POST',
+ success: function(model, response, options){
+ if(!_.isUndefined(model.error)){
+ if(model.errorMessage.search("msg:") != -1){
+ var startIndex = model.errorMessage.search("msg:") + 4;
+ var endIndex = model.errorMessage.split("\n")[0].search("\\)");
+ Utils.notifyError(model.error+":<br/>"+model.errorMessage.substring(startIndex, endIndex));
+ } else {
+ Utils.notifyError(model.error);
+ }
+ } else {
+ Utils.notifySuccess("Topology rebalanced successfully.");
+ }
+ this.closeModal();
+ }.bind(this),
+ error: function(model, response, options){
+ Utils.notifyError("Error occured in rebalancing topology.");
+ }
+ });
+ }
+ },
+ renderSpoutInput: function(){
+ if(this.state.spout){
+ return this.state.spout.map(function(spout, i){
+ return (
+ <div key={i} className="form-group">
+ <label className="control-label col-sm-3">{spout.key}*:</label>
+ <div className="col-sm-9">
+ <input type="number" min="0" name={spout.key} className="form-control" defaultValue={spout.value} required="required"/>
+ </div>
+ </div>
+ );
+ });
+ }
+ },
+ renderBoltInput: function(){
+ if(this.state.bolt){
+ return this.state.bolt.map(function(bolt, i){
+ return (
+ <div key={i} className="form-group">
+ <label className="control-label col-sm-3">{bolt.key}*:</label>
+ <div className="col-sm-9">
+ <input type="number" min="0" name={bolt.key} className="form-control" defaultValue={bolt.value} />
+ </div>
+ </div>
+ );
+ });
+ }
+ },
+ closeModal: function(){
+ $('#'+this.props.modalId).modal("hide");
+ },
+ render: function() {
+ var totalExecutor = this.state.workers + this.state.freeSlots;
+ return (
+ <div className="modal fade" id={this.props.modalId} role="dialog" data-backdrop="static">
+ <div className="modal-dialog">
+ <div className="modal-content">
+ <div className="modal-header">
+ <button type="button" className="close" data-dismiss="modal">×</button>
+ <h4 className="modal-title">Rebalance Topology</h4>
+ </div>
+ <div className="modal-body">
+ <div className="alert alert-danger alert-dismissible error-msg" role="alert">
+ <strong>Warning!</strong> Please fill out all the required (*) fields.
+ </div>
+ <form className="form-horizontal" role="form">
+ <div className="form-group">
+ <label className="control-label col-sm-3">Workers*:</label>
+ <div className="col-sm-9">
+ <b>0</b><input id="ex1" name="workers" data-slider-id='ex1Slider' type="text" /><b>{totalExecutor}</b>
+ </div>
+ </div>
+ {this.renderSpoutInput()}
+ {this.renderBoltInput()}
+ <div className="form-group">
+ <label className="control-label col-sm-3">Wait Time*:</label>
+ <div className="col-sm-9">
+ <input type="number" min="0" name="waitTime" className="form-control" defaultValue={this.state.waitTime}/>
+ </div>
+ </div>
+ </form>
+ </div>
+ <div className="modal-footer">
+ <button type="button" className="btn btn-default" onClick={this.closeModal}>Close</button>
+ <button type="button" className="btn btn-success" onClick={this.rebalanceTopologyAction}>Save</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ },
+ });
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03735c99/contrib/views/storm/src/main/resources/scripts/views/SupervisorSummaryView.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/scripts/views/SupervisorSummaryView.jsx b/contrib/views/storm/src/main/resources/scripts/views/SupervisorSummaryView.jsx
new file mode 100644
index 0000000..5827147
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/scripts/views/SupervisorSummaryView.jsx
@@ -0,0 +1,65 @@
+/**
+ 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([
+ 'jsx!components/Table',
+ 'react',
+ 'react-dom',
+ 'jsx!containers/SupervisorSummary',
+ 'jsx!components/Breadcrumbs'
+ ],function(Table, React, ReactDOM, SupervisorSummary, Breadcrumbs){
+ 'use strict';
+
+ return React.createClass({
+ displayName: 'SupervisorSummaryView',
+ getInitialState: function(){
+ return null;
+ },
+ componentWillMount: function(){
+ $('.loader').show();
+ },
+ componentDidMount: function(){
+ $('.loader').hide();
+ },
+ componentWillUpdate: function(){
+ $('.loader').show();
+ },
+ componentDidUpdate: function(){
+ $('.loader').hide();
+ },
+ render: function() {
+ return (
+ <div>
+ <Breadcrumbs links={this.getLinks()} />
+ <div className="row">
+ <div className="col-sm-12">
+ <SupervisorSummary/>
+ </div>
+ </div>
+ </div>
+ );
+ },
+ getLinks: function() {
+ var links = [
+ {link: '#!/dashboard', title: 'Dashboard'},
+ {link: 'javascript:void(0);', title: 'Supervisor Summary'}
+ ];
+ return links;
+ }
+ });
+});
\ No newline at end of file