http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/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..a6d2ed6 --- /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){ + Utils.notifyError(response.error); + } 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/529ef7f7/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/529ef7f7/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..eb9d0d1 --- /dev/null +++ b/contrib/views/storm/src/main/resources/scripts/views/ProfilingView.jsx @@ -0,0 +1,203 @@ +/** + 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', + 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({ + 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({ + 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/529ef7f7/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..96f01a6 --- /dev/null +++ b/contrib/views/storm/src/main/resources/scripts/views/RebalanceView.jsx @@ -0,0 +1,216 @@ +/** + 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', + 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"> + <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/529ef7f7/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutCollectionView.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutCollectionView.js b/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutCollectionView.js deleted file mode 100644 index eadab4d..0000000 --- a/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutCollectionView.js +++ /dev/null @@ -1,53 +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([ - 'require', - 'views/Spout/SpoutItemView' - ], function(require, vSpoutItemView) { - 'use strict'; - - var spoutCollectionView = Marionette.CollectionView.extend({ - childView: vSpoutItemView, - - childViewOptions: function() { - return { - collection: this.collection, - topologyId: this.topologyId, - systemBoltFlag: this.systemBoltFlag, - windowTimeFrame: this.windowTimeFrame, - emptyMsg: "No spouts" - }; - }, - - initialize: function(options) { - this.collection = options.collection; - if(options.collection.length == 0){ - this.collection.add(new Backbone.Model()); - } - this.topologyId = options.topologyId; - this.systemBoltFlag = options.systemBoltFlag; - this.windowTimeFrame = options.windowTimeFrame; - }, - - onRender: function(){} - - }); - - return spoutCollectionView; -}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutItemView.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutItemView.js b/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutItemView.js deleted file mode 100644 index f41e8d1..0000000 --- a/contrib/views/storm/src/main/resources/scripts/views/Spout/SpoutItemView.js +++ /dev/null @@ -1,355 +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([ - 'require', - 'utils/LangSupport', - 'models/VOutputStat', - 'models/VExecutor', - 'models/VError', - 'utils/TableLayout', - 'hbs!tmpl/spout/spoutItemView' -], function(require, localization, vOutputStat, vExecutors, vError, TableLayout, tmpl) { - 'use strict'; - - var spoutItemView = Marionette.ItemView.extend({ - template: tmpl, - tagName: 'div', - templateHelpers: function() { - return { - id: this.model.id, - name: this.model.get('name') - }; - }, - initialize: function(options) { - this.spoutsCollection = new Backbone.Collection(); - options.model.opstCollection = new Backbone.Collection(); - options.model.extrCollection = new Backbone.Collection(); - options.model.errorCollection = new Backbone.Collection(); - if(!_.isUndefined(options.topologyId) && options.model.has('errorWorkerLogLink')){ - this.getDetails(options.model, options.topologyId, options.systemBoltFlag, options.windowTimeFrame); - } - }, - - events: {}, - - onRender: function() { - this.showSpoutsSummaryTable(); - this.showOpstSummaryTable(); - }, - - getDetails: function(model, topologyId, systemBoltFlag, windowTimeFrame) { - var that = this; - this.spoutsCollection.trigger('request', this.spoutsCollection); - model.getDetails({ - topologyId: topologyId, - spoutId: model.get('spoutId'), - systemBoltFlag: systemBoltFlag, - windowTimeFrame: windowTimeFrame, - success: function(spoutsModel, response, options) { - that.spoutsCollection.trigger('sync', that.spoutsCollection); - if (spoutsModel) { - spoutsModel = new Backbone.Model(spoutsModel); - if (spoutsModel.has('outputStats') && spoutsModel.get('outputStats').length) { - - var arr = []; - _.each(spoutsModel.get('outputStats'), function(object) { - arr.push(new vOutputStat(object)) - }); - model.opstCollection.reset(arr); - } - - if (spoutsModel.has('executorStats') && spoutsModel.get('executorStats').length) { - var arr = []; - _.each(spoutsModel.get('executorStats'), function(object) { - arr.push(new vExecutors(object)) - }); - model.extrCollection.reset(arr); - } - - if (spoutsModel.has('componentErrors') && spoutsModel.get('componentErrors').length) { - - var arr = []; - _.each(spoutsModel.get('componentErrors'), function(object) { - arr.push(new vError(object)) - }); - model.errorCollection.reset(arr); - } - - that.spoutsCollection.reset(model); - } - }, - error: function() { - that.spoutsCollection.trigger('error', that.spoutsCollection); - return null; - } - }); - }, - - showSpoutsSummaryTable: function() { - - this.$('[data-id="SpoutsSummaryTable"]').html(new TableLayout({ - columns: this.getSpoutColumns(), - collection: this.spoutsCollection, - includeFilter: false, - includePagination: false, - includeFooterRecords: false, - gridOpts: { - emptyText: localization.tt('msg.noSpoutFound'), - className: 'table table-borderless table-striped table-header' - } - }).render().$el); - }, - - showOpstSummaryTable: function() { - var that = this; - - that.$('[data-id="OpstSummaryTable"]').html(new TableLayout({ - columns: that.getOpstColumns(), - collection: that.model.opstCollection, - includeFilter: false, - includePagination: false, - includeFooterRecords: false, - gridOpts: { - emptyText: localization.tt('msg.noOutputStatsFound'), - className: 'table table-borderless table-striped' - } - }).render().$el); - - that.$('[data-id="ExtrSummaryTable"]').html(new TableLayout({ - columns: that.getExtrColumns(), - collection: that.model.extrCollection, - includeFilter: false, - includePagination: false, - includeFooterRecords: false, - gridOpts: { - emptyText: localization.tt('msg.noExecutorsFound'), - className: 'table table-borderless table-striped' - } - }).render().$el); - - that.$('[data-id="ErrorSummaryTable"]').html(new TableLayout({ - columns: that.getErrorColumns(), - collection: that.model.errorCollection, - includeFilter: false, - includePagination: false, - includeFooterRecords: false, - gridOpts: { - emptyText: localization.tt('msg.noErrorFound'), - className: 'table table-borderless table-striped' - } - }).render().$el); - }, - - - getSpoutColumns: function() { - var cols = [{ - name: "spoutId", - cell: "string", - label: localization.tt('lbl.id'), - sortable: true, - hasTooltip: true, - tooltipText: localization.tt('msg.spoutId') - }, { - name: "executors", - cell: "string", - label: localization.tt('lbl.executors'), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutExecutors') - }, { - name: "tasks", - cell: "string", - label: localization.tt('lbl.tasks'), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutTasks') - }, { - name: "emitted", - cell: "string", - label: localization.tt('lbl.emitted'), - hasTooltip: true, - tooltipText: localization.tt('msg.emitted') - }, { - name: "transferred", - cell: "string", - label: localization.tt('lbl.transferred'), - hasTooltip: true, - tooltipText: localization.tt('msg.transferred') - }, { - name: "completeLatency", - cell: "string", - label: localization.tt('lbl.completeLatency'), - hasTooltip: true, - tooltipText: localization.tt('msg.completeLatency') - }, { - name: "acked", - cell: "string", - label: localization.tt('lbl.acked'), - hasTooltip: true, - tooltipText: localization.tt('msg.acked') - }, { - name: "failed", - cell: "string", - label: localization.tt('lbl.failed'), - hasTooltip: true, - tooltipText: localization.tt('msg.failed') - }]; - return cols; - }, - - getOpstColumns: function() { - var cols = [{ - name: "stream", - cell: "string", - label: localization.tt('lbl.stream'), - hasTooltip: true, - tooltipText: localization.tt('msg.stream'), - sortable: true - }, { - name: "emitted", - cell: "string", - label: localization.tt('lbl.emitted'), - hasTooltip: true, - tooltipText: localization.tt('msg.emitted') - }, { - name: "transferred", - cell: "string", - label: localization.tt('lbl.transferred'), - hasTooltip: true, - tooltipText: localization.tt('msg.transferred') - }, { - name: "completeLatency", - cell: "string", - label: localization.tt('lbl.completeLatencyMS'), - hasTooltip: true, - tooltipText: localization.tt('msg.completeLatency') - }, { - name: "acked", - cell: "string", - label: localization.tt('lbl.acked'), - hasTooltip: true, - tooltipText: localization.tt('msg.acked') - }, { - name: "failed", - cell: "string", - label: localization.tt('lbl.failed'), - hasTooltip: true, - tooltipText: localization.tt('msg.failed') - }]; - return cols; - }, - - getExtrColumns: function() { - var cols = [{ - name: "id", - cell: "string", - label: localization.tt('lbl.id'), - hasTooltip: true, - tooltipText: localization.tt('msg.uniqueExecutorId'), - sortable: true - }, { - name: "uptime", - cell: "string", - label: localization.tt('lbl.uptime'), - hasTooltip: true, - tooltipText: localization.tt('msg.extensionUptime') - }, { - name: "host", - cell: "string", - label: localization.tt('lbl.host'), - hasTooltip: true, - tooltipText: localization.tt('msg.extensionHost') - }, { - name: "port", - cell: "string", - label: localization.tt('lbl.port'), - hasTooltip: true, - tooltipText: localization.tt('msg.extensionPort') - }, { - name: "emitted", - cell: "string", - label: localization.tt('lbl.emitted'), - hasTooltip: true, - tooltipText: localization.tt('msg.emitted') - }, { - name: "transferred", - cell: "string", - label: localization.tt('lbl.transferred'), - hasTooltip: true, - tooltipText: localization.tt('msg.transferred') - }, { - name: "completeLatency", - cell: "string", - label: localization.tt('lbl.completeLatencyMS'), - hasTooltip: true, - tooltipText: localization.tt('msg.completeLatency') - }, { - name: "acked", - cell: "string", - label: localization.tt('lbl.acked'), - hasTooltip: true, - tooltipText: localization.tt('msg.acked') - }, { - name: "failed", - cell: "string", - label: localization.tt('lbl.failed'), - hasTooltip: true, - tooltipText: localization.tt('msg.failed') - }, { - name: "logs", - cell: "Html", - label: '', - sortable: false, - formatter: _.extend({}, Backgrid.CellFormatter.prototype, { - fromRaw: function(rawValue, model) { - if (model) { - return "<a href="+model.get('workerLogLink')+" target='_blank' class='btn btn-success btn-xs center-block'>"+localization.tt('lbl.viewLogs')+"</a>"; - } - } - }) - }]; - return cols; - }, - - getErrorColumns: function() { - var cols = [ - { - name: "time", - cell: "string", - label: localization.tt('lbl.time'), - sortable: true - }, { - name: "errorHost", - cell: "string", - label: localization.tt('lbl.errorHost'), - }, { - name: "errorPort", - cell: "string", - label: localization.tt('lbl.errorPort'), - }, { - name: "error", - cell: "string", - label: localization.tt('lbl.error'), - } - ]; - return cols; - }, - - onClose: function() {} - }); - return spoutItemView; -}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/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 http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/contrib/views/storm/src/main/resources/scripts/views/Topology/RebalanceForm.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Topology/RebalanceForm.js b/contrib/views/storm/src/main/resources/scripts/views/Topology/RebalanceForm.js deleted file mode 100644 index 6de7ca8..0000000 --- a/contrib/views/storm/src/main/resources/scripts/views/Topology/RebalanceForm.js +++ /dev/null @@ -1,129 +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(['utils/LangSupport', - 'utils/Globals', - 'hbs!tmpl/topology/rebalanceForm', - 'models/Cluster', - 'backbone.forms' - ], function (localization, Globals, tmpl, clusterModel) { - 'use strict'; - - var RebalanceForm = Backbone.Form.extend({ - - template: tmpl, - - initialize: function (options) { - this.showSpout = false, - this.showBolt = false, - this.spoutData = [], - this.boltData = []; - this.spoutCollection = options.spoutCollection; - this.boltCollection = options.boltCollection; - this.model = options.model; - this.schemaObj = this.generateSchema(); - this.templateData = { - "spoutFlag": this.showSpout, - "boltFlag": this.showBolt, - "spoutData": this.spoutData, - "boltData": this.boltData, - "showParallelism": (this.showSpout && this.showBolt) ? true : false - }, - Backbone.Form.prototype.initialize.call(this, options); - }, - - generateSchema: function(){ - var that = this; - var freeSlots = this.getClusterSummary(new clusterModel()); - freeSlots += this.model.get('workers'); - var obj = { - workers: { - type: 'RangeSlider', - title: localization.tt('lbl.workers'), - options: {"min":0,"max":freeSlots} - } - }; - - if(that.spoutCollection.length){ - _.each(that.spoutCollection.models, function(model){ - if(model.has('spoutId')) { - that.showSpout = true; - var name = model.get('spoutId'); - obj[name] = { - type: 'Number', - title: name - }; - that.spoutData.push(name); - that.model.set(name,model.get('executors')); - } - }); - } - if(that.boltCollection.length){ - _.each(that.boltCollection.models, function(model){ - if(model.has('boltId')) { - var name = model.get('boltId'); - that.showBolt = true; - obj[name] = { - type: 'Number', - title: name - }; - that.boltData.push(name); - that.model.set(name,model.get('executors')); - } - }); - } - obj.waitTime = { - type: 'Number', - title: localization.tt('lbl.waitTime')+'*', - validators: ['required'] - } - return obj; - }, - - getClusterSummary: function(model){ - var freeSlots = 0; - model.fetch({ - async: false, - success: function(model, response, options) { - if (model) { - freeSlots = model.get('slotsFree'); - } - } - }); - return freeSlots; - }, - - schema: function () { - return this.schemaObj; - }, - - render: function(options){ - Backbone.Form.prototype.render.call(this,options); - }, - - getData: function () { - return this.getValue(); - }, - - close: function () { - console.log('Closing form view'); - } - }); - - return RebalanceForm; -}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyDetail.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyDetail.js b/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyDetail.js deleted file mode 100644 index 29c722e..0000000 --- a/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyDetail.js +++ /dev/null @@ -1,736 +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(['require', - 'utils/Globals', - 'modules/Vent', - 'utils/LangSupport', - 'models/VTopology', - 'models/VSpout', - 'models/VBolt', - 'models/VTopologyConfig', - 'views/Spout/SpoutCollectionView', - 'views/Topology/TopologyGraphView', - 'utils/TableLayout', - 'utils/Utils', - 'bootbox', - 'hbs!tmpl/topology/topologyDetail' -], function(require, Globals, vent, localization, vTopology, vSpout, vBolt, vTopologyConfig, vSpoutCollectionView, vTopologyGraphView, TableLayout, Utils, bootbox, tmpl) { - - 'use strict'; - - var masterView = Marionette.LayoutView.extend({ - - template: tmpl, - - regions: { - 'rTopologyDetailsTbl': '#topologyDetail', - 'rTopologyGraph': '#graph', - 'rTopologySummary': '#topo-summary', - 'rSpoutsTable': '#SpoutsTable', - 'rBoltsSummaryTable': '#BoltsSummaryTable', - 'rTopologyConfigTable': '#TopologyConfigTable' - }, - - ui: { - topologySummary: '#topo-summary', - summaryLoader: '#summaryLoader', - BoltsSummaryDetails: '[data-id="BoltsSummary"]' - }, - - events: { - 'change #tFrame': 'evChangeTimeFrame', - 'click #btnActivate': 'evActivateTopology', - 'click #btnDeactivate': 'evDeactivateTopology', - 'click #btnRebalance': 'evRebalanceTopology', - 'click #btnKill': 'evKillTopology', - 'click #openAll': 'openAllTables', - 'change #sysBolt': 'evSysBoltToggle' - }, - - initialize: function(options) { - this.model = new vTopology(); - this.systemBoltFlag = false; - this.topologyDetailsColl = new Backbone.Collection(); - this.summaryArr = []; - this.windowTimeFrame = ":all-time"; - this.fetchData(options.id, this.systemBoltFlag, this.windowTimeFrame); - this.generateTemplate(); - - this.spoutsCollection = new Backbone.Collection(); - this.boltsCollection = new Backbone.Collection(); - this.topoConfigCollection = new Backbone.Collection(); - }, - - fetchData: function(id, flag, timeFrame){ - var that = this; - $('.loading').show(); - this.model.getDetails({ - id: id, - sysBoltFlag: flag, - windowTimeFrame: timeFrame, - success: function(model, response, options) { - vent.trigger('LastUpdateRefresh', true); - that.model = new vTopology(model); - that.getDetails(that.model); - that.render(); - that.disableBtnAction(model.status); - that.$('#sysBolt').prop("checked",that.systemBoltFlag) - $('.loading').hide(); - }, - error: function(model, response, options) { - vent.trigger('LastUpdateRefresh', true); - $('.loading').hide(); - Utils.notifyError(model.statusText); - } - }); - }, - - generateTemplate: function() { - this.summaryTemplate = _.template('<table class="table table-borderless"><tbody>' + - '<tr>' + - '<th>'+localization.tt('lbl.emitted')+'</th>' + - '<td><%if (emitted) { %> <%=emitted%> <%} else { %> 0 <% } %></td>' + - '</tr>' + - '<tr>' + - '<th>'+localization.tt('lbl.transferred')+'</th>' + - '<td><%if (transferred) { %> <%=transferred%> <%} else { %> 0 <% } %></td>' + - '</tr>' + - '<tr>' + - '<th>'+localization.tt('lbl.completeLatency')+'</th>' + - '<td><%if (completeLatency) { %> <%=completeLatency%> <%} else { %> 0 <% } %></td>' + - '</tr>' + - '<tr>' + - '<th>'+localization.tt('lbl.acked')+'</th>' + - '<td><%if (acked) { %> <%=acked%> <%} else { %> 0 <% } %></td>' + - '</tr>' + - '<tr>' + - '<th>'+localization.tt('lbl.failed')+'</th>' + - '<td><%if (failed) { %> <%=failed%> <%} else { %> 0 <% } %></td>' + - '</tr>' + - '</tbody></table>'); - - }, - - onRender: function() { - if(! this.$el.hasClass('topologyDetailView')){ - this.$el.addClass('topologyDetailView'); - } - this.$('.topology-title').html(this.model.has('name') ? this.model.get('name') : ''); - this.showDetailsTable(this.topologyDetailsColl); - if(!_.isUndefined(this.summaryArr) && this.summaryArr.length){ - this.setTimeFrameOptions(); - } else { - $('.loading').hide(); - } - - this.windowTimeFrame = this.model.get('window'); - this.$('#tFrame').val(this.windowTimeFrame); - this.showSummaryTable(this.windowTimeFrame); - - this.showBoltsSummaryTable(); - this.showTopologyConfigTable(); - - this.showSpoutsSummaryTable(); - - if(this.model.has('id')){ - this.rTopologyGraph.show(new vTopologyGraphView({ - id: this.model.get('id') - })); - } - - this.$('.collapse').on('shown.bs.collapse', function() { - $(this).parent().find(".fa-plus-square").removeClass("fa-plus-square").addClass("fa-minus-square"); - }).on('hidden.bs.collapse', function() { - $(this).parent().find(".fa-minus-square").removeClass("fa-minus-square").addClass("fa-plus-square"); - }); - - this.$('[data-id="r_tableList"]').parent().removeClass('col-md-12'); - if(this.$el.parent().hasClass('active')){ - this.showBreadCrumbs(); - } - }, - - disableBtnAction: function(status){ - _.each(this.$el.find('.btn.btn-success.btn-sm'),function(elem){ - $(elem).removeAttr('disabled'); - }); - switch(status){ - case "ACTIVE": - this.$el.find('#btnActivate').attr('disabled','disabled'); - break; - case "INACTIVE": - this.$el.find('#btnDeactivate').attr('disabled','disabled'); - break; - case "REBALANCING": - this.$el.find('#btnRebalance').attr('disabled','disabled'); - break; - case "KILLED": - this.$el.find('#btnKill').attr('disabled','disabled'); - break; - } - }, - - setTimeFrameOptions: function() { - var html = ''; - _.each(this.summaryArr, function(obj){ - switch(obj.window){ - case "600": - obj.windowPretty = localization.tt('lbl.last10Min'); - break; - case "10800": - obj.windowPretty = localization.tt('lbl.last3Hr'); - break; - case "86400": - obj.windowPretty = localization.tt('lbl.last1Day'); - break; - } - html += "<option value="+obj.window+">"+obj.windowPretty+"</option>"; - }); - this.$('#tFrame').append(html); - }, - - evChangeTimeFrame: function(e) { - this.windowTimeFrame = $(e.currentTarget).val(); - this.fetchData(this.model.get('id'), this.systemBoltFlag, this.windowTimeFrame); - }, - - getDetails: function(model) { - var detModel = new Backbone.Model(_.pick(model.attributes, 'name', 'id', 'owner', 'status', 'uptime', 'workersTotal', 'executorsTotal', 'tasksTotal', 'schedulerInfo')); - - this.topologyDetailsColl.reset(detModel); - - this.summaryArr = model.get('topologyStats'); - - var that = this; - - var spoutsModel = new vSpout(); - - var s_arr = []; - _.each(model.get('spouts'), function(spout){ - var spoutsModel = new vSpout(spout); - s_arr.push(spoutsModel) - }); - that.spoutsCollection.reset(s_arr); - - var b_arr =[]; - _.each(this.model.get("bolts"), function(object){ - b_arr.push(new vBolt(object)); - }); - that.boltsCollection.reset(b_arr); - - var topoConfigModel = this.model.get("configuration"); - if (model) { - var arr = []; - for(var key in topoConfigModel){ - var obj = {}; - obj.key = key; - obj.value = topoConfigModel[key]; - arr.push(new vTopologyConfig(obj)); - } - that.topoConfigCollection.reset(arr); - } - }, - - showSpoutsSummaryTable: function() { - this.rSpoutsTable.show(new vSpoutCollectionView({ - collection: this.spoutsCollection, - systemBoltFlag: this.systemBoltFlag, - topologyId: this.model.get('id'), - windowTimeFrame: this.windowTimeFrame - })); - }, - - showBoltsSummaryTable: function() { - - this.rBoltsSummaryTable.show(new TableLayout({ - columns: this.getBoltColumns(), - collection: this.boltsCollection, - includeFilter: false, - includePagination: false, - includeFooterRecords: false, - gridOpts: { - emptyText: localization.tt('msg.noBoltFound'), - className: 'table table-borderless table-striped cluster-table' - } - })); - }, - - showTopologyConfigTable: function() { - this.rTopologyConfigTable.show(new TableLayout({ - columns: this.getTopoConfigColumns(), - collection: this.topoConfigCollection, - includeFilter: false, - includePagination: false, - includeFooterRecords: false, - gridOpts: { - emptyText: localization.tt('msg.noTopologyConfigFound'), - className: 'table table-borderless table-striped cluster-table' - } - })); - }, - - - showDetailsTable: function(collection) { - this.rTopologyDetailsTbl.show(new TableLayout({ - columns: this.getColumns(), - collection: collection, - gridOpts: { - className: 'table table-bordered table-striped backgrid' - } - })); - }, - - getColumns: function() { - this.countActive = 0; - var cols = [{ - name: "name", - cell: "string", - label: localization.tt("lbl.name"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryName') - }, { - name: "id", - cell: "string", - label: localization.tt("lbl.id"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryId') - }, { - name: "owner", - cell: "string", - label: localization.tt("lbl.owner"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryOwner') - }, { - name: "status", - cell: "string", - label: localization.tt("lbl.status"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryStatus') - }, { - name: "uptime", - cell: "string", - label: localization.tt("lbl.uptime"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryUptime') - }, { - name: "workersTotal", - cell: "string", - label: "# "+localization.tt("lbl.workers"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryWorkers') - }, { - name: "executorsTotal", - cell: "string", - label: "# "+localization.tt("lbl.executors"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryExecutors') - }, { - name: "tasksTotal", - cell: "string", - label: "# "+localization.tt("lbl.tasks"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryTasks') - }, { - name: "schedulerInfo", - cell: "string", - label: localization.tt("lbl.schedulerInfo"), - hasTooltip: true, - tooltipText: localization.tt('msg.topologySummaryScheduler') - }]; - return cols; - }, - - showSummaryTable: function(id) { - var object = _.findWhere(this.summaryArr, { - window: id - }); - if(_.isNull(object) || _.isUndefined(object)){ - object = {}; - object.emitted = 0; - object.transferred = 0; - object.completeLatency = 0; - object.acked = 0; - object.failed = 0; - object.window = id; - } - this.ui.topologySummary.html(this.summaryTemplate(object)); - }, - - evActivateTopology: function() { - var that = this; - bootbox.confirm({ - message: localization.tt('dialogMsg.activateTopologyMsg'), - buttons: { - confirm: { - label: localization.tt('btn.yes'), - className: "btn-success", - }, - cancel: { - label: localization.tt('btn.no'), - className: "btn-default", - } - }, - callback: function(result){ - if(result){ - that.model.activateTopology({ - id: that.model.get('id'), - success: function(model, response, options){ - Utils.notifySuccess(localization.tt('dialogMsg.topologyActivateSuccessfully')); - that.fetchData(that.model.get('id'), that.systemBoltFlag, that.windowTimeFrame); - }, - error: function(model, response, options){ - Utils.notifyError(model.statusText); - } - }); - } - } - }); - - }, - evDeactivateTopology: function() { - var that = this; - bootbox.confirm({ - message: localization.tt('dialogMsg.deactivateTopologyMsg'), - buttons: { - confirm: { - label: localization.tt('btn.yes'), - className: "btn-success", - }, - cancel: { - label: localization.tt('btn.no'), - className: "btn-default", - } - }, - callback: function(result){ - if(result){ - that.model.deactivateTopology({ - id: that.model.get('id'), - success: function(model, response, options){ - Utils.notifySuccess(localization.tt('dialogMsg.topologyDeactivateSuccessfully')); - that.fetchData(that.model.get('id'), that.systemBoltFlag, that.windowTimeFrame); - }, - error: function(model, response, options){ - Utils.notifyError(model.statusText); - } - }); - } - } - }); - }, - evRebalanceTopology: function() { - var that = this; - if(this.view){ - this.onDialogClosed(); - } - require(['views/Topology/RebalanceForm'], function(rebalanceForm){ - var reBalanceModel = new Backbone.Model(); - reBalanceModel.set('workers',that.topologyDetailsColl.models[0].get('workersTotal')); - reBalanceModel.set('waitTime',30); - that.view = new rebalanceForm({ - spoutCollection : that.spoutsCollection, - boltCollection: that.boltsCollection, - model: reBalanceModel - }); - that.view.render(); - - bootbox.dialog({ - message: that.view.$el, - title: localization.tt('lbl.rebalanceTopology'), - className: "rebalance-modal", - buttons: { - cancel: { - label: localization.tt('btn.cancel'), - className: "btn-default", - callback: function(){ - that.onDialogClosed(); - } - }, - success: { - label: localization.tt('btn.apply'), - className: "btn-success", - callback: function(){ - var err = that.view.validate(); - if(_.isEmpty(err)){ - that.rebalanceTopology(); - } else return false; - } - } - } - }); - }); - }, - rebalanceTopology: function(){ - var that = this, - attr = this.view.getValue(), - obj = {"rebalanceOptions":{}}; - - if(!_.isUndefined(attr.workers.min) && !_.isNull(attr.workers.min)){ - obj.rebalanceOptions.numWorkers = attr.workers.min; - } - - var spoutBoltObj = {}; - for(var key in attr){ - if(!_.isEqual(key,'workers') && !_.isEqual(key,'waitTime')){ - if(!_.isNull(attr[key])){ - spoutBoltObj[key] = attr[key]; - } - } - } - - if(_.keys(spoutBoltObj).length){ - obj.rebalanceOptions.executors = spoutBoltObj; - } - - $.ajax({ - url: Globals.baseURL + '/api/v1/topology/' + that.model.get('id') + '/rebalance/' + attr.waitTime, - data: (_.keys(obj.rebalanceOptions).length) ? JSON.stringify(obj) : 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(localization.tt('dialogMsg.topologyRebalanceSuccessfully')); - that.fetchData(that.model.get('id'), that.systemBoltFlag, that.windowTimeFrame); - } - }, - error: function(model, response, options){ - Utils.notifyError(model.statusText); - } - }); - }, - evKillTopology: function() { - var that = this; - bootbox.prompt({ - title: localization.tt('dialogMsg.killTopologyMsg'), - value: "30", - buttons: { - confirm: { - label: localization.tt('btn.yes'), - className: "btn-success", - }, - cancel: { - label: localization.tt('btn.no'), - className: "btn-default", - } - }, - callback: function(result) { - if(result != null){ - that.model.killTopology({ - id: that.model.get('id'), - waitTime: result, - success: function(model, response, options){ - Utils.notifySuccess(localization.tt('dialogMsg.topologyKilledSuccessfully')); - that.fetchData(that.model.get('id'), that.systemBoltFlag, that.windowTimeFrame); - }, - error: function(model, response, options){ - Utils.notifyError(model.statusText); - } - }); - } - } - }); - }, - onDialogClosed: function() { - if (this.view) { - this.view.close(); - this.view.remove(); - this.view = null; - } - }, - openAllTables: function(){ - console.log("Open All !!"); - }, - evSysBoltToggle: function(e){ - this.systemBoltFlag = $(e.currentTarget).is(':checked'); - this.fetchData(this.model.get('id'), this.systemBoltFlag, this.windowTimeFrame); - }, - - getSpoutColumns: function() { - var cols = [{ - name: "spoutId", - cell: "string", - label: localization.tt("lbl.id"), - sortable: true, - hasTooltip: true, - tooltipText: localization.tt('msg.spoutId') - }, - { - name: "executors", - cell: "string", - label: localization.tt("lbl.executors"), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutExecutors'), - sortable: true - }, - { - name: "tasks", - cell: "string", - label: localization.tt("lbl.tasks"), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutTask'), - sortable: true, - }, - { - name: "emitted", - cell: "string", - label: localization.tt("lbl.emitted"), - hasTooltip: true, - tooltipText: localization.tt('msg.emitted'), - sortable: true - }, - { - name: "transferred", - cell: "string", - label: localization.tt("lbl.transferred"), - hasTooltip: true, - tooltipText: localization.tt('msg.transferred'), - sortable: true - }, - { - name: "completeLatency", - cell: "string", - label: localization.tt("lbl.completeLatency"), - hasTooltip: true, - tooltipText: localization.tt('msg.completeLatency'), - sortable: true - }, - { - name: "acked", - cell: "string", - label: localization.tt("lbl.acked"), - hasTooltip: true, - tooltipText: localization.tt('msg.acked'), - sortable: true - }, - { - name: "failed", - cell: "string", - label: localization.tt("lbl.failed"), - hasTooltip: true, - tooltipText: localization.tt('msg.failed'), - sortable: true - } - ]; - return cols; - }, - - getBoltColumns: function() { - var cols = [{ - name: "boltId", - cell: "string", - label: localization.tt('lbl.id'), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutId'), - sortable: true - }, { - name: "executors", - cell: "string", - label: localization.tt('lbl.executors'), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutExecutors'), - }, { - name: "tasks", - cell: "string", - label: localization.tt('lbl.tasks'), - hasTooltip: true, - tooltipText: localization.tt('msg.spoutTasks'), - }, { - name: "emitted", - cell: "string", - label: localization.tt('lbl.emitted'), - hasTooltip: true, - tooltipText: localization.tt('msg.emitted'), - }, { - name: "transferred", - cell: "string", - label: localization.tt('lbl.transferred'), - hasTooltip: true, - tooltipText: localization.tt('msg.transferred'), - }, { - name: "capacity", - cell: "string", - label: localization.tt('lbl.capacity'), - formatter: _.extend({}, Backgrid.CellFormatter.prototype, { - fromRaw: function(rawValue, model) { - if (model) { - return (parseFloat(model.attributes.capacity) < 1 ? " < 1%" : " "+model.get('capacity')+" %"); - } - } - }), - hasTooltip: true, - tooltipText: localization.tt('msg.boltCapacity'), - }, { - name: "executeLatency", - cell: "string", - label: localization.tt('lbl.executeLatency'), - hasTooltip: true, - tooltipText: localization.tt('msg.boltExecuteLatency'), - }, { - name: "executed", - cell: "string", - label: localization.tt('lbl.executed'), - hasTooltip: true, - tooltipText: localization.tt('msg.boltExected'), - }, { - name: "processLatency", - cell: "string", - label: localization.tt('lbl.processLatency'), - hasTooltip: true, - tooltipText: localization.tt('msg.boltProcessLatency'), - }, { - name: "acked", - cell: "string", - label: localization.tt('lbl.acked'), - hasTooltip: true, - tooltipText: localization.tt('msg.boltAcked'), - }]; - return cols; - }, - - showBreadCrumbs: function(){ - vent.trigger('Breadcrumb:Show', (this.model.has('name')) ? this.model.get('name') : 'No-Name'); - }, - - getTopoConfigColumns: function() { - var cols = [ - { - name: "key", - cell: "string", - label: localization.tt('lbl.key'), - sortable: true - }, { - name: "value", - cell: "string", - label: localization.tt('lbl.value'), - } - ]; - return cols; - } - - }); - return masterView; -}); http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyForm.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyForm.js b/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyForm.js deleted file mode 100644 index d08e353..0000000 --- a/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyForm.js +++ /dev/null @@ -1,102 +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(['utils/LangSupport', - 'utils/Globals', - 'hbs!tmpl/topology/topologyForm', - 'backbone.forms' - ], function (localization, Globals, tmpl) { - 'use strict'; - - // var tmpl = ; - var TopologyForm = Backbone.Form.extend({ - - template: tmpl, - - initialize: function (options) { - Backbone.Form.prototype.initialize.call(this, options); - this.bindEvents(); - }, - - bindEvents: function () { - console.log('Binding Events Here !!'); - }, - - schema: function () { - return { - name: { - type: 'Text', - title: localization.tt('lbl.name')+'*', - editorClass : 'form-control', - validators: ['required'] - }, - jar: { - type: 'Fileupload', - title: localization.tt('lbl.jar')+'*', - validators: ['required'] - }, - // nimbusHostname: { - // type: 'Select', - // title: localization.tt('lbl.nimbusHostname')+'*', - // options: [{ - // val: '', - // label: '--' - // }, - // { - // val: '1', - // label: 'Hostname 1' - // }, { - // val: '2', - // label: 'Hostname 2' - // }, { - // val: '3', - // label: 'Hostname 3' - // }], - // editorClass : 'form-control', - // validators: ['required'] - // }, - topologyClass: { - type: 'Text', - title: localization.tt('lbl.topologyClass')+'*', - editorClass : 'form-control', - validators: ['required'] - }, - arguments: { - type: 'Text', - title: localization.tt('lbl.arguments'), - editorClass : 'form-control', - // validators: ['required'] - } - }; - }, - - // render: function (options) { - // Backbone.Form.prototype.render.call(this, options); - // }, - - getData: function () { - return this.getValue(); - }, - - close: function () { - console.log('Closing form view'); - } - }); - - return TopologyForm; -}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/529ef7f7/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyGraphView.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyGraphView.js b/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyGraphView.js deleted file mode 100644 index 01fccc9..0000000 --- a/contrib/views/storm/src/main/resources/scripts/views/Topology/TopologyGraphView.js +++ /dev/null @@ -1,423 +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(['require', 'utils/Globals'], function(require, Globals) { - 'use strict' - - var topologyGraphView = Marionette.LayoutView.extend({ - template: _.template('<div><canvas id="topoGraph" width="844" height="260"></div>'), - - initialize: function(options) { - this.topologyId = options.id; - }, - onRender: function() { - var topology_data, - that = this; - var sys = arbor.ParticleSystem(20, 1000, 0.15, true, 55, 0.02, 0.6); - sys.renderer = this.renderGraph('#topoGraph'); - sys.stop(); - - Backbone.ajax({ - url: Globals.baseURL + "/api/v1/topology/" + this.topologyId + "/visualization", - success: function(data, status, jqXHR) { - if(_.isString(data)){ - data = JSON.parse(data); - } - topology_data = data; - that.update_data(topology_data, sys); - sys.renderer.signal_update(); - sys.renderer.redraw(); - that.rechoose(topology_data, sys, 'default') - } - }); - - }, - - renderGraph: function(elem) { - var canvas = this.$(elem).get(0); - var ctx = canvas.getContext("2d"); - var gfx = arbor.Graphics(canvas); - var psys; - - var totaltrans = 0; - var weights = {}; - var texts = {}; - var update = false; - var that = this; - var myRenderer = { - init: function(system) { - psys = system; - psys.screenSize(canvas.width, canvas.height) - psys.screenPadding(20); - myRenderer.initMouseHandling(); - }, - - signal_update: function() { - update = true; - }, - - redraw: function() { - if (!psys) - return; - - if (update) { - totaltrans = that.calculate_total_transmitted(psys); - weights = that.calculate_weights(psys, totaltrans); - texts = that.calculate_texts(psys, totaltrans); - update = false; - } - - - - ctx.fillStyle = "white"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - var x = 0; - - - psys.eachEdge(function(edge, pt1, pt2) { - - var len = Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); - var sublen = len - (Math.max(50, 20 + gfx.textWidth(edge.target.name)) / 2); - var thirdlen = len / 3; - var theta = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x); - - var newpt2 = { - x: pt1.x + (Math.cos(theta) * sublen), - y: pt1.y + (Math.sin(theta) * sublen) - }; - - var thirdpt = { - x: pt1.x + (Math.cos(theta) * thirdlen), - y: pt1.y + (Math.sin(theta) * thirdlen) - } - - var weight = weights[edge.source.name + edge.target.name]; - - if (!weights[edge.source.name + edge.target.name]) { - totaltrans = that.calculate_total_transmitted(psys); - weights = that.calculate_weights(psys, totaltrans); - } - - ctx.strokeStyle = "rgba(0,0,0, .333)"; - ctx.lineWidth = 25 * weight + 5; - ctx.beginPath(); - - var arrlen = 15; - ctx.moveTo(pt1.x, pt1.y); - ctx.lineTo(newpt2.x, newpt2.y); - ctx.lineTo(newpt2.x - arrlen * Math.cos(theta - Math.PI / 6), newpt2.y - arrlen * Math.sin(theta - Math.PI / 6)); - ctx.moveTo(newpt2.x, newpt2.y); - ctx.lineTo(newpt2.x - arrlen * Math.cos(theta + Math.PI / 6), newpt2.y - arrlen * Math.sin(theta + Math.PI / 6)); - - - if (texts[edge.source.name + edge.target.name] == null) { - totaltrans = calculate_total_transmitted(psys); - texts = calculate_texts(psys, totaltrans); - } - - gfx.text(texts[edge.source.name + edge.target.name], thirdpt.x, thirdpt.y + 10, { - color: "black", - align: "center", - font: "Arial", - size: 10 - }) - ctx.stroke(); - }); - - psys.eachNode(function(node, pt) { - var col; - - var real_trans = that.gather_stream_count(node.data[":stats"], "default", "600"); - - if (node.data[":type"] === "bolt") { - var cap = Math.min(node.data[":capacity"], 1); - var red = Math.floor(cap * 225) + 30; - var green = Math.floor(255 - red); - var blue = Math.floor(green / 5); - col = arbor.colors.encode({ - r: red, - g: green, - b: blue, - a: 1 - }); - } else { - col = "#0000FF"; - } - - var w = Math.max(55, 25 + gfx.textWidth(node.name)); - - gfx.oval(pt.x - w / 2, pt.y - w / 2, w, w, { - fill: col - }); - gfx.text(node.name, pt.x, pt.y + 3, { - color: "white", - align: "center", - font: "Arial", - size: 12 - }); - gfx.text(node.name, pt.x, pt.y + 3, { - color: "white", - align: "center", - font: "Arial", - size: 12 - }); - - gfx.text(_.isEqual(parseFloat(node.data[":latency"]).toFixed(2), 'NaN') ? "0.00 ms" : parseFloat(node.data[":latency"]).toFixed(2) + " ms", pt.x, pt.y + 17, { - color: "white", - align: "center", - font: "Arial", - size: 12 - }); - - }); - - // Draw heatmap - var rect_x = canvas.width - 250, - rect_y = canvas.height - 30, - colorArr = ['#ff0000', '#F7BF43', '#F4EA47', '#A4CC45', '#1EE12D']; - ctx.fillStyle = "grey"; - ctx.fillText("Heatmap", rect_x, rect_y - 5) - ctx.rect(rect_x, rect_y, 245, canvas.height - 50); - var grd = ctx.createLinearGradient(rect_x, rect_y, canvas.width - 5, rect_y); - grd.addColorStop(0.000, colorArr[0]); - grd.addColorStop(0.250, colorArr[1]); - grd.addColorStop(0.500, colorArr[2]); - grd.addColorStop(0.750, colorArr[3]); - grd.addColorStop(1.000, colorArr[4]); - ctx.fillStyle = grd; - ctx.fillRect(rect_x, rect_y, 245, canvas.height - 50); - - //Draw legends - var legendX = canvas.width - 140, - legendY = canvas.height - 170, - legendWidth = 30, - legendY1 = canvas.height - 240, - legendTextX = canvas.width - 105, - legendTextY = canvas.height - 160, - legendTextArr = ['0% - 20%', '21% - 40%', '41% - 60%', '61% - 80%', '81% - 100%']; - - for (var i = 0; i < colorArr.length; i++) { - ctx.rect(legendX, legendY + (i * 25), legendWidth, legendY1); - ctx.fillStyle = colorArr[4 - i]; - ctx.fillRect(legendX, legendY + (i * 25), legendWidth, legendY1); - ctx.fillStyle = "black"; - ctx.fillText(legendTextArr[i], legendTextX, legendTextY + (i * 25)); - } - - }, - - initMouseHandling: function() { - var dragged = null; - var clicked = false; - var _mouseP; - - var handler = { - clicked: function(e) { - var pos = $(canvas).offset(); - _mouseP = arbor.Point(e.pageX - pos.left, e.pageY - pos.top); - dragged = psys.nearest(_mouseP); - - if (dragged && dragged.node !== null) { - dragged.node.fixed = true; - } - - clicked = true; - setTimeout(function() { - clicked = false; - }, 50); - - $(canvas).bind('mousemove', handler.dragged); - $(window).bind('mouseup', handler.dropped); - - return false; - }, - - dragged: function(e) { - - var pos = $(canvas).offset(); - var s = arbor.Point(e.pageX - pos.left, e.pageY - pos.top); - - if (dragged && dragged.node != null) { - var p = psys.fromScreen(s); - dragged.node.p = p; - } - - return false; - - }, - - dropped: function(e) { - // if (clicked) { - // if (dragged.distance < 50) { - // if (dragged && dragged.node != null) { - // window.location = dragged.node.data[":link"]; - // } - // } - // } - - if (dragged === null || dragged.node === undefined) return; - if (dragged.node !== null) dragged.node.fixed = false; - dragged.node.tempMass = 1000; - dragged = null; - $(canvas).unbind('mousemove', handler.dragged); - $(window).unbind('mouseup', handler.dropped); - _mouseP = null; - return false; - } - - } - - $(canvas).mousedown(handler.clicked); - } - }; - - this.calculate_texts = function(psys, totaltrans) { - var texts = {}; - psys.eachEdge(function(edge, pt1, pt2) { - var text = ""; - for (var i = 0; i < edge.target.data[":inputs"].length; i++) { - var stream = edge.target.data[":inputs"][i][":stream"]; - var sani_stream = edge.target.data[":inputs"][i][":sani-stream"]; - if (edge.target.data[":inputs"][i][":component"] === edge.source.name) { - var stream_transfered = that.gather_stream_count(edge.source.data[":stats"], sani_stream, "600"); - text += stream + ": " + stream_transfered + ": " + (totaltrans > 0 ? Math.round((stream_transfered / totaltrans) * 100) : 0) + "%\n"; - - } - } - - texts[edge.source.name + edge.target.name] = text; - }); - - return texts; - }; - - this.calculate_weights = function(psys, totaltrans) { - var weights = {}; - - psys.eachEdge(function(edge, pt1, pt2) { - var trans = 0; - for (var i = 0; i < edge.target.data[":inputs"].length; i++) { - var stream = edge.target.data[":inputs"][i][":sani-stream"]; - if (edge.target.data[":inputs"][i][":component"] === edge.source.name) - trans += that.gather_stream_count(edge.source.data[":stats"], stream, "600"); - } - weights[edge.source.name + edge.target.name] = (totaltrans > 0 ? trans / totaltrans : 0); - }); - return weights; - }; - - this.calculate_total_transmitted = function(psys) { - var totaltrans = 0; - var countedmap = {} - psys.eachEdge(function(node, pt, pt2) { - if (!countedmap[node.source.name]) - countedmap[node.source.name] = {}; - - for (var i = 0; i < node.target.data[":inputs"].length; i++) { - var stream = node.target.data[":inputs"][i][":stream"]; - if (that.stream_checked(node.target.data[":inputs"][i][":sani-stream"])) { - if (!countedmap[node.source.name][stream]) { - if (node.source.data[":stats"]) { - var toadd = that.gather_stream_count(node.source.data[":stats"], node.target.data[":inputs"][i][":sani-stream"], "600"); - totaltrans += toadd; - } - countedmap[node.source.name][stream] = true; - } - } - } - - }); - - return totaltrans; - }; - - this.stream_checked = function(stream) { - // var checked = $("#" + stream).is(":checked"); - var checked = _.isEqual(stream.substr(0, 7), 'default'); - return checked; - }; - - this.gather_stream_count = function(stats, stream, time) { - var transferred = 0; - if (stats) - for (var i = 0; i < stats.length; i++) { - if (stats[i][":transferred"] != null) { - var stream_trans = stats[i][":transferred"][time][stream]; - if (stream_trans != null) - transferred += stream_trans; - } - } - return transferred; - }; - - return myRenderer; - }, - update_data: function(jdat, sys) { - _.each(jdat, function(k, v) { - if (sys.getNode(k)) - sys.getNode(k).data = v; - }); - }, - - has_checked_stream_input: function(inputs) { - for (var i = 0; i < inputs.length; i++) { - var x = this.stream_checked(inputs[i][":sani-stream"]); - if (x) - return true; - } - return false; - }, - - has_checked_stream_output: function(jdat, component) { - var that = this; - var ret = false; - $.each(jdat, function(k, v) { - for (var i = 0; i < v[":inputs"].length; i++) { - if (that.stream_checked(v[":inputs"][i][":sani-stream"]) && v[":inputs"][i][":component"] == component) - ret = true; - } - }); - return ret; - }, - - rechoose: function(jdat, sys, box) { - var that = this; - //Check each node in our json data to see if it has inputs from or outputs to selected streams. If it does, add a node for it. - $.each(jdat, function(k, v) { - if (that.has_checked_stream_input(v[":inputs"]) || that.has_checked_stream_output(jdat, k)) - sys.addNode(k, v); - }); - - //Check each node in our json data and add necessary edges based on selected components. - $.each(jdat, function(k, v) { - for (var i = 0; i < v[":inputs"].length; i++) - if (_.isEqual(v[":inputs"][i][":sani-stream"].substr(0, 7), 'default')) { - sys.addEdge(v[":inputs"][i][":component"], k, v); - } - }); - - //Tell the particle system's renderer that it needs to update its labels, colors, widths, etc. - sys.renderer.signal_update(); - sys.renderer.redraw(); - - } - - }); - - return topologyGraphView; -}); \ No newline at end of file
