Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 471D2200D26 for ; Fri, 20 Oct 2017 21:50:14 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 45A2D1609ED; Fri, 20 Oct 2017 19:50:14 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 4DF17160BEE for ; Fri, 20 Oct 2017 21:50:12 +0200 (CEST) Received: (qmail 29010 invoked by uid 500); 20 Oct 2017 19:50:11 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 28886 invoked by uid 99); 20 Oct 2017 19:50:11 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 20 Oct 2017 19:50:11 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id A0525DFC4A; Fri, 20 Oct 2017 19:50:10 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: yusaku@apache.org To: commits@ambari.apache.org Date: Fri, 20 Oct 2017 19:50:13 -0000 Message-Id: In-Reply-To: <2930440e9f51419faeb555aec23fba40@git.apache.org> References: <2930440e9f51419faeb555aec23fba40@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [04/22] ambari git commit: AMBARI-21955. Update React version to 15.6.2 to get MIT license. (Sanket Shah via yusaku) archived-at: Fri, 20 Oct 2017 19:50:14 -0000 http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/Editable.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/Editable.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/Editable.jsx new file mode 100644 index 0000000..03005ae --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/Editable.jsx @@ -0,0 +1,127 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import ReactDOM, {findDOMNode} from 'react-dom'; +import {Overlay, Popover, Button} from 'react-bootstrap'; + +export default class Editable extends Component { + state = { + edit: false, + errorMsg: '' + }; + + handleClick = () => { + let state = this.state; + state.edit = true; + this.setState(state); + } + + handleResolve = () => { + const {resolve} = this.props; + if (resolve) { + resolve(this); + } + } + + handleReject = () => { + const {reject} = this.props; + if (reject) { + reject(this); + } else { + this.hideEditor(); + } + } + + hideEditor = () => { + let state = this.state; + state.edit = false; + this.setState(state); + } + + getValueString() { + const {children} = this.props; + + if (children.type == 'input' || children.type == 'textarea') { + return children.props.value || children.props.defaultValue; + } else if (children.type == 'select') {} else { + var fn = children.getStringValue; + if (fn) { + return fn(); + } else { + console.error('Custom component must have getValueString() function.'); + } + } + } + + anchorStyle = { + textDecoration: 'none', + borderBottom: 'dashed 1px #0088cc', + cursor: 'pointer', + color: '#323133' + }; + + render() { + const {children, showButtons, inline, placement, title} = this.props; + const {edit, errorMsg} = this.state; + + const buttons = showButtons + ? ([, + + ]) + : null; + + const error = errorMsg + ? ( +
{errorMsg}
+ ) + : null; + + const popover = ( + + {children} + {buttons} + {error} + + ); + + return ( +
+ {edit && inline + ? null + : {this.getValueString()} +} + {edit && inline + ? [children, buttons, error] + : ReactDOM.findDOMNode(this.refs.target)} {...this.props}> + {popover} + +} +
+ ); + } +} + +Editable.defaultProps = { + showButtons: true, + inline: false, + placement: "top" +}; http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSModel.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSModel.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSModel.jsx new file mode 100644 index 0000000..14cb17d --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSModel.jsx @@ -0,0 +1,149 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import {Modal, Button} from 'react-bootstrap'; + +const defaultState = { + show: false, + title: '', + btnOkText: 'Ok', + btnCancelText: 'Cancel' +}; + +export default class FSModal extends Component { + state = defaultState; + show() { + var state = state || {}; + state.show = true; + this.setState(state); + } + sure() { + let resolve = this.props["data-resolve"]; + if (resolve) { + resolve(); + } + } + cancel() { + let reject = this.props["data-reject"]; + if (reject) { + reject(); + } else { + this.hide(); + } + } + hide() { + this.setState({show: false}); + } + header() { + return ( + + + {this.props["data-title"]} + + + ); + } + body() { + return ( + + {this.props.children} + + ); + } + footer() { + return ( + + { + this.props.hideCloseBtn + ? null + : + } + { + this.props.hideOkBtn + ? null + : + } + + ); + } + render() { + return ( + + {this.props.hideHeader + ? '' + : this.header()} + {this.body()} + {this.props.hideFooter + ? '' + : this.footer()} + + ); + } +} + +var _resolve; +var _reject; + +export class Confirm extends FSModal { + show(state) { + var state = state || {}; + state.show = true; + this.setState(state); + let promise = new Promise(function(resolve, reject) { + _resolve = resolve; + _reject = reject; + }); + return promise; + } + sure() { + _resolve(this); + } + cancel() { + _reject(this); + this.setState(defaultState); + } + header() { + return ( + + + {this.state.title} + + + ); + } + body() { + return ''; + } + footer() { + return ( + + + + + ); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/LogLevelComponent.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/LogLevelComponent.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/LogLevelComponent.jsx new file mode 100644 index 0000000..fb8e10b --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/LogLevelComponent.jsx @@ -0,0 +1,236 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import TopologyREST from '../rest/TopologyREST'; +import { + Table, + Thead, + Th, + Tr, + Td, + unsafe +} from 'reactable'; +import CommonPagination from './CommonPagination'; +import {toastOpt,pageSize} from '../utils/Constants'; +import Select from 'react-select'; +import FSReactToastr from './FSReactToastr'; +import CommonNotification from './CommonNotification'; +import Editable from './Editable'; + +export default class LogLevelComponent extends Component{ + constructor(props){ + super(props); + this.state = { + logLevelObj : {}, + traceOption : this.populateTraceOptions(), + selectedKeyName : 'com.your.organization.LoggerName', + selectedTrace : 'ALL', + selectedTimeOut : 30 + }; + this.fetchData(); + this.keyName = ''; + this.timeChange=''; + } + + fetchData = () => { + const {topologyId} = this.props; + TopologyREST.getLogConfig(topologyId).then((result) => { + if(result.errorMessage !== undefined){ + FSReactToastr.error( + , '', toastOpt); + } else { + let stateObj={}; + stateObj.selectedKeyName = 'com.your.organization.LoggerName'; + stateObj.selectedTrace = 'ALL'; + stateObj.selectedTimeOut = 30; + stateObj.logLevelObj = result.namedLoggerLevels; + this.setState(stateObj); + } + }); + } + + populateTraceOptions = () => { + let temp=[]; + const arr = ['ALL','TRACE','DEBUG','INFO','WARN','ERROR','FATAL','OFF']; + _.map(arr, (a) => { + temp.push({label : a, value : a}); + }); + return temp; + } + + handleNameChange = (e) => { + this.keyName = e.target.value.trim(); + } + + handleTimeChange = (e) => { + this.timeChange = e.target.value.trim(); + } + + traceLavelChange = (type,key,addRow,obj) => { + let tempObj = _.cloneDeep(this.state.logLevelObj); + let tempKeyName = 'ALL'; + if(!!addRow){ + tempKeyName = obj.value; + } else{ + tempObj[type][key] = obj.value; + } + this.setState({logLevelObj : tempObj,selectedTrace : tempKeyName}); + } + + modifyCommonObjValue = (refType,type,key,action,addRow) => { + let logObj = _.cloneDeep(this.state.logLevelObj); + let tempTimeOut = _.cloneDeep(this.state.selectedTimeOut); + const timeValue = (this.timeChange === '' || this.timeChange === undefined) ? parseInt(this.refs[refType].defaultValue || 0,10) : parseInt(this.timeChange,10); + if(action === 'save' && addRow === null){ + logObj[type][key] = timeValue; + } else if(action === 'save' && !!addRow){ + tempTimeOut = timeValue; + this.timeChange = ''; + }else if(action === 'reject'){ + this.timeChange = parseInt(this.refs[refType].defaultValue || 0,10); + } + this.refs[refType].hideEditor(); + this.setState({logLevelObj : logObj ,selectedTimeOut :tempTimeOut }); + } + + getDateFormat = (str) => { + const d = new Date(str); + return d.toLocaleDateString() + ' ' + d.toLocaleTimeString(); + } + + saveAndClearLogConfig = (type,action) => { + let tempObj = _.cloneDeep(this.state.logLevelObj); + let obj={},namedLoggerLevels={}; + obj.namedLoggerLevels={}; + if(action === 'clear'){ + obj.namedLoggerLevels[type] = {}; + obj.namedLoggerLevels[type].timeout = 0; + obj.namedLoggerLevels[type].target_level = null; + } else { + obj.namedLoggerLevels[type] = tempObj[type]; + } + obj.namedLoggerLevels[type].reset_level = 'INFO'; + delete obj.namedLoggerLevels[type].timeout_epoch; + + this.callLogConfigAPI(obj,null,action); + } + + callLogConfigAPI = (obj,addRow,action) => { + const {topologyId,logConfig} = this.props; + const {logLevelObj} = this.state; + TopologyREST.postLogConfig(topologyId, {body : JSON.stringify(obj)}).then((result) => { + if(result.errorMessage !== undefined){ + this.setState({logLevelObj : logConfig}); + FSReactToastr.error( + , '', toastOpt); + } else { + let msg = !!addRow ? "Log configuration added successfully" : (action === 'save' ? "Log configuration applied successfully." : "Log configuration cleared successfully."); + FSReactToastr.success({msg}); + this.fetchData(); + } + }); + } + + addLoggerName = (refType,action) => { + let tempName = _.cloneDeep(this.state.selectedKeyName); + if(action === 'save'){ + tempName = !!this.keyName ? this.keyName : tempName; + }else if(action === 'reject'){ + this.keyName = this.refs[refType].defaultValue || tempName; + } + this.refs[refType].hideEditor(); + this.setState({selectedKeyName : tempName}); + } + + addLogRow = () => { + const {selectedKeyName,selectedTrace,selectedTimeOut} = this.state; + let obj={}; + obj.namedLoggerLevels = {}; + obj.namedLoggerLevels[selectedKeyName] = {}; + obj.namedLoggerLevels[selectedKeyName].target_level = selectedTrace; + obj.namedLoggerLevels[selectedKeyName].reset_level = 'INFO'; + obj.namedLoggerLevels[selectedKeyName].timeout = selectedTimeOut; + this.callLogConfigAPI(obj,'addRow'); + } + + render(){ + const {logLevelObj,traceOption,selectedKeyName,selectedTrace,selectedTimeOut} = this.state; + return( +
+
+

Change Log Level

+

Modify the logger levels for topology. Note that applying a setting restarts the timer in the workers. To configure the root logger, use the name ROOT.

+ + + + + + + + + { + _.map(_.keys(logLevelObj), (logKey, i) => { + return + + + + + + ; + }) + } + + + + + + + +
LoggerLevelTimeoutExpires AtAction
+ {logKey} + {} + + + + + {this.getDateFormat(logLevelObj[logKey].timeout_epoch)} + +   + + +
+ + + + + + + + +   + +   + +
+
+ ); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/ProfilingView.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/ProfilingView.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/ProfilingView.jsx new file mode 100644 index 0000000..eedf0dd --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/ProfilingView.jsx @@ -0,0 +1,168 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import TopologyREST from '../rest/TopologyREST'; +import { + Table, + Thead, + Th, + Tr, + Td, + unsafe +} from 'reactable'; +import {toastOpt,pageSize} from '../utils/Constants'; +import Utils from '../utils/Utils'; +import FSReactToastr from '../components/FSReactToastr'; +import CommonNotification from '../components/CommonNotification'; +import _ from 'lodash'; + +export default class ProfilingView extends Component{ + constructor(props){ + super(props); + this.state = { + currentPage : 1, + executorArr : this.props.executorStats ? this.fetchData() : [], + selectedWorker : [], + selectAll : false, + warnMsg : false, + successMsg : false, + errorMsg : false + }; + } + + fetchData = () => { + const {executorStats} = this.props; + let data = {},executorArr=[]; + _.map(executorStats, (o) => { + const hostPort = o.host + ":" + o.port; + if(!data[hostPort]){ + data[hostPort] = {}; + } + if(!data[hostPort].idArr){ + data[hostPort].idArr = []; + } + data[hostPort].idArr.push(o.id); + }); + let keys = this.hostPortArr = _.keys(data); + _.map(keys, (k) => { + executorArr.push({ + hostPort: k, + executorId: data[k].idArr, + checked : false + }); + }); + return executorArr; + } + + commonBtnAction = (actionType) => { + const {selectedWorker} = this.state; + selectedWorker.length ? this.apiCallback(actionType) : this.setState({warnMsg : true,successMsg : false,errorMsg: false}); + } + + apiCallback = (actionType) => { + const {topologyId} = this.props; + const {selectedWorker} = this.state; + let promiseArr=[]; + _.map(selectedWorker, (w) => { + promiseArr.push(TopologyREST.getProfiling(topologyId,actionType,w.hostPort)); + }); + + Promise.all(promiseArr).then((results) => { + _.map(results, (r) => { + let tempErrorMsg= false,tempSuccessMsg=false; + if(r.errorMessage !== undefined){ + tempErrorMsg = true; + tempSuccessMsg: false; + } else { + tempErrorMsg = false; + tempSuccessMsg: true; + } + this.setState({successMsg : tempSuccessMsg,errorMsg: tempErrorMsg,warnMsg : false}); + }); + }); + } + + handleChange = (hostPort) => { + let tempSelect = _.cloneDeep(this.state.selectAll); + let tempExecutor=_.cloneDeep(this.state.executorArr); + let tempWorker = _.cloneDeep(this.state.selectedWorker); + if(!!hostPort){ + const ind = _.findIndex(tempExecutor, (e) => {return e.hostPort === hostPort; }); + const index = _.findIndex(tempWorker,(t) => {return t.hostPort === hostPort;}); + if(index === -1 && ind !== -1){ + tempWorker.push(tempExecutor[ind]); + } else { + tempWorker.splice(index,1); + } + tempExecutor[ind].checked = !tempExecutor[ind].checked; + } else { + tempSelect = !this.state.selectAll; + _.map(tempExecutor,(t) => { + t.checked = tempSelect; + }); + tempWorker = tempExecutor; + } + this.setState({selectedWorker : tempWorker,selectAll : tempSelect,executorArr :tempExecutor }); + } + + render(){ + const {currentPage,executorArr,selectAll,warnMsg,successMsg,errorMsg} = this.state; + return( +
+
+ Warning! Please select atleast one worker to perform operation. +
+
+ Success! Action performed successfully. +
+
+ Error! Error occured while performing the action. +
+
+
+ + + +
+
+
+ + + + + + + { + _.map(executorArr , (e,i) => { + return + + + + ; + }) + } +
+ + Host:PortExecutor Id
+ + {e.hostPort}{e.executorId.join(',')}
+
+ ); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/RadialChart.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/RadialChart.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/RadialChart.jsx new file mode 100644 index 0000000..4c4e8fc --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/RadialChart.jsx @@ -0,0 +1,134 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import ReactDOM from 'react-dom'; +import d3 from 'd3'; +import d3Tip from 'd3-tip'; + + +export default class RadialChart extends Component { + static propTypes = { + data: PropTypes.array.isRequired, + labels: PropTypes.array.isRequired, + width: PropTypes.number, + height: PropTypes.number, + innerRadius: PropTypes.number.isRequired, + outerRadius: PropTypes.number.isRequired, + color: PropTypes.array + } + constructor(props) { + super(props); + this.const = { + tau: 2 * Math.PI, + width: props.width || "44", + height: props.height || "52", + innerRadius: parseInt(props.innerRadius, 10) || 20, + outerRadius: parseInt(props.outerRadius, 10) || 25, + color: props.color || d3.scale.category20() + }; + this.arc = d3.svg.arc() + .innerRadius(this.const.innerRadius) + .outerRadius(this.const.outerRadius) + .startAngle(0); + } + componentDidUpdate() { + this.animateGraph(); + } + componentDidMount() { + const self = this; + this.tip = d3Tip() + .attr('class', 'd3-tip') + .offset([-10, 0]) + .html(function() { + var text = "
" + this.props.labels[0] + ": " + this.props.data[0] + "
"; + text += "
Free: " + (parseInt(this.props.data[1], 10) - parseInt(this.props.data[0], 10)) + "
"; + text += "
" + this.props.labels[1] + ": " + this.props.data[1] + "
"; + return text; + }.bind(this)); + var svg = this.svg = d3.select(ReactDOM.findDOMNode(this)) + .attr('width', this.const.width + "px") + .attr('height', this.const.height + "px") + .append('g').attr('transform', 'translate(' + (this.const.width / 2) + ', ' + (this.const.height / 2) + ')'); + + this.text = svg.append("text") + .attr("y", "0.3em") + .attr("class", "graphVal") + .attr("text-anchor", "middle") + .attr("font-size", this.const.fontSize) + .on("mouseover", function(d){ + self.tip.show(d, this); + }) + .on("mouseout", function(d){ + self.tip.hide(d, this); + }) + .text("0"); + + var background = svg.append("path") + .datum({ + endAngle: this.const.tau + }) + .style("fill", this.const.color[0]) + .attr("d", this.arc); + + this.foreground = svg.append("path") + .datum({ + endAngle: 0 + }) + .style("fill", function(d, i) { + return this.const.color[1]; + }.bind(this)) + .attr("d", this.arc); + this.svg.call(this.tip); + // $('#container').append($('body > .d3-tip')); + this.animateGraph(); + } + animateGraph() { + var percent = (parseInt(this.props.data[0], 10) / parseInt(this.props.data[1], 10) * 100); + if (percent) { + percent = percent.toFixed(0) + ' %'; + } else { + percent = '0 %'; + } + + d3.select(ReactDOM.findDOMNode(this)).select('.graphVal').text(percent); + + var newValue = this.props.data[0] / this.props.data[1] * 100; + this.foreground.transition() + .duration(750) + .call(this._arcTween.bind(this), this.const.tau * (newValue / 100)); + } + _arcTween(transition, newAngle) { + var arc = this.arc; + transition.attrTween("d", function(d) { + var interpolate = d3.interpolate(d.endAngle, newAngle); + return function(t) { + d.endAngle = interpolate(t); + if (!d.endAngle) { + d.endAngle = 0; + } + return arc(d); + }; + + }); + } + render() { + return ( < svg className = "radial-chart" > < /svg>); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/RebalanceTopology.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/RebalanceTopology.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/RebalanceTopology.jsx new file mode 100644 index 0000000..43c7f78 --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/RebalanceTopology.jsx @@ -0,0 +1,152 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import TopologyREST from '../rest/TopologyREST'; +import {toastOpt,pageSize} from '../utils/Constants'; +import Utils from '../utils/Utils'; +import FSReactToastr from './FSReactToastr'; +import CommonNotification from './CommonNotification'; +import _ from 'lodash'; + +export default class RebalanceTopology extends Component{ + constructor(props){ + super(props); + this.state = { + freeSlot : 0, + waitTime : 30, + rebalanceData : {} + }; + this.fetchData(); + } + + fetchData = () => { + const {topologyExecutors,spoutArr,boltArr} = this.props; + TopologyREST.getSummary('cluster').then((result) => { + if(result.errorMessage !== undefined){ + FSReactToastr.error( + , '', toastOpt); + } else { + let stateObj = {}; + stateObj.freeSlot = result.slotsFree; + stateObj.rebalanceData={}; + stateObj.rebalanceData.workers = topologyExecutors + stateObj.freeSlot; + this.totalWorker = stateObj.rebalanceData.workers; + _.map(spoutArr, (s) => { + stateObj.rebalanceData[s.spoutId] = s.executors; + }); + _.map(boltArr, (b) => { + stateObj.rebalanceData[b.boltId] = b.executors; + }); + this.setState(stateObj); + } + }); + } + + rebalanceValueChange = (type,e) => { + let data = _.cloneDeep(this.state.rebalanceData); + data[type] = +e.target.value; + this.setState({rebalanceData : data}); + } + + waitTimeChange = (e) => { + this.setState({waitTime : +e.target.value}); + } + + validateData = () => { + const {rebalanceData,waitTime} = this.state; + let errorFlag = []; + _.map(_.keys(rebalanceData), (key) => { + if(rebalanceData[key] === ''){ + errorFlag.push(false); + } + }); + if(waitTime === ''){ + errorFlag.push(false); + } + return errorFlag.length ? false : true; + } + + handleSave = () => { + const {rebalanceData,waitTime} = this.state; + const {topologyId} = this.props; + let finalData = { + "rebalanceOptions": { + "executors": {} + } + }; + _.map(_.keys(rebalanceData), (key) => { + if(key === "workers"){ + finalData.rebalanceOptions.numWorkers = rebalanceData[key]; + } else { + finalData.rebalanceOptions.executors[key] = rebalanceData[key]; + } + }); + + return TopologyREST.postActionOnTopology(topologyId,'rebalance',waitTime,{body : JSON.stringify(finalData)}); + } + + render(){ + const {freeSlot,waitTime,rebalanceData} = this.state; + const {spoutArr,boltArr}= this.props; + return( +
+
+
+ +
+
+ 0{this.totalWorker} +
+
+ { + _.map(spoutArr, (s , i) => { + return
+
+ +
+
+ +
+
; + }) + } + { + _.map(boltArr, (b , n) => { + return
+
+ +
+
+ +
+
; + }) + } +
+
+ +
+
+ +
+
+
+ ); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/SearchLogs.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/SearchLogs.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/SearchLogs.jsx new file mode 100644 index 0000000..ebf1615 --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/SearchLogs.jsx @@ -0,0 +1,84 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import {baseUrl} from '../utils/Constants'; +import {DropdownButton, FormGroup, Checkbox} from 'react-bootstrap'; +import fetch from 'isomorphic-fetch'; + +export default class SearchLogs extends Component{ + render() { + return ( +
+
+ +
+
+
+ + + Search archived logs + + + Deep search + + +
+ +
+
+
+
+ ); + } + handleSearch(){ + var searchBoxEl = document.getElementById('searchBox'); + var searchArchivedLogsEl = document.getElementById('searchArchivedLogs'); + var deepSearchEl = document.getElementById('deepSearch'); + var topologyId = this.props.id; + + fetch(baseUrl.replace('proxy?url=/api/v1/', 'storm_details'), {"credentials": "same-origin"}) + .then((response) => { + return response.json(); + }) + .then((response) => { + var url = response.hostdata+'/'; + if(deepSearchEl.checked == true){ + url += "deep_search_result.html"; + }else{ + url += "search_result.html"; + } + url += '?search='+searchBoxEl.value+'&id='+ topologyId +'&count=1'; + if(searchArchivedLogsEl.checked == true){ + if(deepSearchEl.checked == true){ + url += "&search-archived=on"; + }else{ + url += "&searchArchived=checked"; + } + } + window.open(url, '_blank'); + + searchBoxEl.value = ''; + searchArchivedLogsEl.checked = false; + deepSearchEl.checked = false; + }); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/components/TopologyGraph.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/TopologyGraph.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/TopologyGraph.jsx new file mode 100644 index 0000000..4cfc6bb --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/TopologyGraph.jsx @@ -0,0 +1,208 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import ReactDOM from 'react-dom'; +import d3 from 'd3'; +import d3Tip from 'd3-tip'; +import dagreD3 from 'dagre-d3/dist/dagre-d3'; + +export default class TopologyGraph extends Component{ + static propTypes = { + data: PropTypes.object.isRequired, + width: PropTypes.string, + height: PropTypes.string + } + constructor(props) { + super(props); + this.syncData(this.props.data); + this.updateFlag = true; + } + componentDidUpdate() { + if(!!this.updateFlag){ + this.syncData(this.props.data); + this.updateGraph(); + } + } + componentWillReceiveProps(nextProps){ + _.isEqual(nextProps.data,this.props.data) ? this.updateFlag = false : this.updateFlag = true; + } + componentDidMount(){ + var that = this; + this.svg = d3.select(ReactDOM.findDOMNode(this)); + //Set up tooltip + this.tooltip = d3Tip() + .attr('class', function() { + return 'd3-tip testing'; + }) + .offset([-10, 0]) + .html(function(data) { + var d = that.g.node(data); + var tip = "
    "; + if (d[":capacity"] !== null){ tip += "
  • Capacity: " + d[":capacity"].toFixed(2) + "
  • ";} + if (d[":latency"] !== null){ tip += "
  • Latency: " + d[":latency"].toFixed(2) + "
  • ";} + if (d[":transferred"] !== null){ tip += "
  • Transferred: " + d[":transferred"].toFixed(2) + "
  • ";} + tip += "
"; + return tip; + }); + //Set up zoom + this.zoom = d3.behavior.zoom() + .scaleExtent([0, 8]) + .on("zoom", this.zoomed.bind(this)); + } + zoomed(){ + this.inner.attr("transform", + "translate(" + this.zoom.translate() + ")" + + "scale(" + this.zoom.scale() + ")" + ); + } + // update graph (called when needed) + updateGraph(){ + var that = this; + var g = ReactDOM.findDOMNode(this).children[0]; + if(g){ + g.remove(); + } + var inner = this.inner = this.svg.append("g"); + // Create the renderer + var render = new dagreD3.render(); + render.arrows().arrowPoint = (parent, id, edge, type) => { + var marker = parent.append("marker") + .attr("id", id) + .attr("viewBox", "0 0 10 10") + .attr("refX", 5) + .attr("refY", 5) + .attr("markerUnits", "strokeWidth") + .attr("markerWidth", 6) + .attr("markerHeight", 6.5) + .attr("orient", "auto"); + var path = marker.append("path") + .attr("d", "M 0 0 L 10 5 L 0 10 z") + .style("stroke-width", 1) + .style("stroke-dasharray", "1,0") + .style("fill", "grey") + .style("stroke", "grey"); + dagreD3.util.applyStyle(path, edge[type + "Style"]); + }; + + render.shapes().img = (parent, bbox, node) => { + var shapeSvg; + if(parent){ + shapeSvg = parent.insert("image") + .attr("class", "nodeImage") + .attr("xlink:href", function(d) { + if (node) { + if(node.type === 'spout'){ + return "styles/img/icon-spout.png"; + } else if(node.type === 'bolt'){ + return "styles/img/icon-bolt.png"; + } + } + }).attr("x", "-12px") + .attr("y", "-12px") + .attr("width", "30px") + .attr("height", "30px"); + } + node.intersect = function(point) { + return dagreD3.intersect.circle(node, 20, point); + }; + return shapeSvg; + }; + this.svg.call(this.zoom).call(this.tooltip); + // Run the renderer. This is what draws the final graph. + render(inner, this.g); + + inner.selectAll("g.nodes image") + .on('mouseover', function(d) { + that.tooltip.show(d, this); + }) + .on('mouseout', function(d) { + that.tooltip.hide(this); + }); + inner.selectAll("g.nodes g.label") + .attr("transform", "translate(2,-30)"); + // Center the graph + var initialScale = 1; + var svgWidth = this.svg[0][0].parentNode.clientWidth; + var svgHeight = this.svg[0][0].parentNode.clientHeight; + if(this.linkArray.length > 0){ + this.zoom.translate([(svgWidth - this.g.graph().width * initialScale) / 2, (svgHeight - this.g.graph().height * initialScale) / 2]) + .scale(initialScale) + .event(this.svg); + } + } + syncData(data){ + this.nodeArray = []; + this.linkArray = []; + this.g = new dagreD3.graphlib.Graph().setGraph({ + nodesep: 50, + ranksep: 190, + rankdir: "LR", + marginx: 20, + marginy: 20 + // transition: function transition(selection) { + // return selection.transition().duration(500); + // } + }); + if(data){ + var keys = _.keys(data); + keys.map(function(key){ + if(!key.startsWith('__')){ + data[key].id = key; + data[key].type = data[key][":type"]; + this.nodeArray.push(data[key]); + } + }.bind(this)); + + var spoutObjArr = _.filter(this.nodeArray, { "type": "spout" }); + if (spoutObjArr.length > 1) { + for(var i = 0; i < spoutObjArr.length; i++){ + spoutObjArr[i].x = 50; + spoutObjArr[i].y = parseInt(i+'10', 10); + spoutObjArr[i].fixed = true; + } + } else if (spoutObjArr.length == 1) { + spoutObjArr[0].x = 50; + spoutObjArr[0].y = 100; + spoutObjArr[0].fixed = true; + } + + this.nodeArray.map(function(node){ + var inputArr = node[":inputs"] || []; + inputArr.map(function(input){ + if(!input[":component"].startsWith("__")){ + var sourceNode = _.find(this.nodeArray, {id: input[":component"]}); + this.linkArray.push({ + source: sourceNode, + target: node + }); + this.g.setNode(sourceNode.id, _.extend(sourceNode, {label: sourceNode.id, shape: 'img'})); + this.g.setNode(node.id, _.extend(node, {label: node.id, shape: 'img'})); + this.g.setEdge(sourceNode.id, node.id, {"arrowhead": 'arrowPoint'}); + } + }.bind(this)); + }.bind(this)); + } + } + render() { + return ( + + ); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/containers/BaseContainer.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/containers/BaseContainer.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/containers/BaseContainer.jsx new file mode 100644 index 0000000..62846b7 --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/containers/BaseContainer.jsx @@ -0,0 +1,50 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import Footer from '../components/Footer'; +import {Confirm} from '../components/FSModel'; + +export default class BaseContainer extends Component { + + constructor(props) { + super(props); + } + + handleKeyPress = (event) => { + event.key === "Enter" + ? this.refs.Confirm.state.show + ? this.refs.Confirm.sure() + : '' + :event.key === "Escape" + ? this.refs.Confirm.state.show + ? this.refs.Confirm.cancel() + : '' + :''; + } + + render() { + return ( +
+ {this.props.children} + +
+
+ ); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e88526e7/contrib/views/storm/src/main/resources/ui/app/scripts/containers/ClusterSummary.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/containers/ClusterSummary.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/containers/ClusterSummary.jsx new file mode 100644 index 0000000..904ed68 --- /dev/null +++ b/contrib/views/storm/src/main/resources/ui/app/scripts/containers/ClusterSummary.jsx @@ -0,0 +1,125 @@ +/** + 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. +**/ + +import React, {Component} from 'react'; +import RadialChart from '../components/RadialChart'; +import FSReactToastr from '../components/FSReactToastr'; +import {toastOpt} from '../utils/Constants'; +import TopologyREST from '../rest/TopologyREST'; +import NimbusSummary from './NimbusSummary'; +import CommonNotification from '../components/CommonNotification'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; + +export default class ClusterSummary extends Component{ + constructor(props){ + super(props); + this.fetchData(); + this.state = { + entity :{} + }; + } + + fetchData = () => { + TopologyREST.getSummary('cluster').then((result) => { + if(result.errorMessage !== undefined){ + FSReactToastr.error( + , '', toastOpt); + } else { + this.setState({entity : result}); + } + }); + } + render(){ + const {entity} = this.state; + return( +
+
+
+ Executors are threads in a Worker process.}> +
+
Executor
+
+ + {entity.executorsTotal} +
+
+
+
+
+ A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.}> +
+
Tasks
+
+ + {entity.tasksTotal} +
+
+
+
+
+
+
+ The number of nodes in the cluster currently.}> +
+
Supervisor
+
+
+ +
+
+
+
+
+
+ Slots are Workers (processes).}> +
+
Slots
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+ ); + } +}