You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2017/10/24 20:14:46 UTC
[25/50] [abbrv] ambari git commit: AMBARI-21955. Update React version
to 15.6.2 to get MIT license. (Sanket Shah via yusaku)
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/contrib/views/storm/src/main/resources/ui/app/scripts/components/CommonWindowPanel.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/CommonWindowPanel.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/CommonWindowPanel.jsx
new file mode 100644
index 0000000..0f8130f
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/CommonWindowPanel.jsx
@@ -0,0 +1,99 @@
+/**
+ 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 Select from 'react-select';
+import CommonSwitchComponent from './CommonSwitchComponent';
+import {OverlayTrigger, Tooltip} from 'react-bootstrap';
+
+export default class CommonWindowPanel extends Component{
+ constructor(props){
+ super(props);
+ }
+
+ windowChange = (obj) => {
+ this.props.handleWindowChange(obj);
+ }
+
+ commonToggleChange = (params) => {
+ this.props.toggleSystem(params);
+ }
+
+ commonTopologyActionHandler = (action) => {
+ this.props.handleTopologyAction(action);
+ }
+
+ render(){
+ const {selectedWindowKey,windowOptions,systemFlag,debugFlag,handleLogLevel,topologyStatus,KYC,handleProfiling} = this.props;
+ return(
+ <div className="form-group no-margin">
+ <label className="col-sm-1 control-label">Window</label>
+ <div className="col-sm-2">
+ <Select value={selectedWindowKey} options={windowOptions} onChange={this.windowChange.bind(this)} valueKey="label" labelKey="label" clearable={false}/>
+ </div>
+ <label className="col-sm-2 control-label">System Summary</label>
+ <div className="col-sm-2">
+ <CommonSwitchComponent checked={systemFlag} switchCallBack={this.commonToggleChange.bind(this,'systemFlag')}/>
+ </div>
+ <label className="col-sm-1 control-label">Debug</label>
+ <div className="col-sm-1">
+ <CommonSwitchComponent checked={debugFlag} switchCallBack={this.commonToggleChange.bind(this,'debugFlag')}/>
+ </div>
+ <div className="col-sm-3 text-right">
+ <div className="btn-group" role="group">
+ {
+ KYC === 'detailView'
+ ? [ <OverlayTrigger key={1} placement="top" overlay={<Tooltip id="tooltip1">Activate</Tooltip>}>
+ <button type="button" className="btn btn-primary" onClick={this.commonTopologyActionHandler.bind(this,'activate')} disabled={topologyStatus === 'ACTIVE' ? "disabled" : null}>
+ <i className="fa fa-play"></i>
+ </button>
+ </OverlayTrigger>,
+ <OverlayTrigger key={2} placement="top" overlay={<Tooltip id="tooltip1">Deactivate</Tooltip>}>
+ <button type="button" className="btn btn-primary" onClick={this.commonTopologyActionHandler.bind(this,'deactivate')} disabled={topologyStatus === 'INACTIVE' ? "disabled" : null}>
+ <i className="fa fa-stop"></i>
+ </button>
+ </OverlayTrigger>,
+ <OverlayTrigger key={3} placement="top" overlay={<Tooltip id="tooltip1">Rebalance</Tooltip>}>
+ <button type="button" className="btn btn-primary" onClick={this.commonTopologyActionHandler.bind(this,'rebalance')} disabled={topologyStatus === 'REBALANCING' ? "disabled" : null}>
+ <i className="fa fa-balance-scale"></i>
+ </button>
+ </OverlayTrigger>,
+ <OverlayTrigger key={4} placement="top" overlay={<Tooltip id="tooltip1">Kill</Tooltip>}>
+ <button type="button" className="btn btn-primary" onClick={this.commonTopologyActionHandler.bind(this,'kill')} disabled={topologyStatus === 'KILLED' ? "disabled" : null}>
+ <i className="fa fa-ban"></i>
+ </button>
+ </OverlayTrigger>,
+ <OverlayTrigger key={5} placement="top" overlay={<Tooltip id="tooltip1">Change Log Level</Tooltip>}>
+ <button type="button" className="btn btn-primary" onClick={handleLogLevel}>
+ <i className="fa fa-file-o"></i>
+ </button>
+ </OverlayTrigger>
+ ]
+ : <OverlayTrigger placement="top" overlay={<Tooltip id="tooltip1">Profiling & Debugging</Tooltip>}>
+ <button type="button" className="btn btn-primary" onClick={handleProfiling}>
+ <i className="fa fa-cogs"></i>
+ </button>
+ </OverlayTrigger>
+
+ }
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/contrib/views/storm/src/main/resources/ui/app/scripts/components/CustomToastContainer.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/CustomToastContainer.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/CustomToastContainer.jsx
new file mode 100644
index 0000000..04456cb
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/CustomToastContainer.jsx
@@ -0,0 +1,41 @@
+/**
+ 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 { render } from 'react-dom';
+import ReactToastr, {ToastMessage, ToastContainer} from "react-toastr";
+
+class CustomToastContainer extends ToastContainer{
+ success(msg, title, opts){
+ super.success(msg.props.children, msg, opts);
+ }
+
+ error(msg, title, opts){
+ super.error(msg.props.children, msg, opts);
+ }
+
+ info(msg, title, opts){
+ super.info(msg.props.children, msg, opts);
+ }
+
+ warning(msg, title, opts){
+ super.warning(msg.props.children, msg, opts);
+ }
+}
+
+export default CustomToastContainer;
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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
+ ? ([<Button className="btn-primary btn-sm" onClick={this.handleResolve} key="resolve" style={{margin : "0 0 3px 5px"}}>
+ <i className="fa fa-check"></i></Button>,
+ <Button className="btn-default btn-sm" onClick={this.handleReject} key="reject" style={{margin : "0 3px"}}>
+ <i className="fa fa-times"></i>
+ </Button>
+ ])
+ : null;
+
+ const error = errorMsg
+ ? (
+ <div className="editable-error">{errorMsg}</div>
+ )
+ : null;
+
+ const popover = (
+ <Popover id="popover-positioned-left" title={title || ''}>
+ {children}
+ {buttons}
+ {error}
+ </Popover>
+ );
+
+ return (
+ <div className="editable-container" style={{display: 'inline'}} id={this.props.id || ''}>
+ {edit && inline
+ ? null
+ : <a ref="target" onClick={this.handleClick} style={this.anchorStyle}>{this.getValueString()}</a>
+}
+ {edit && inline
+ ? [children, buttons, error]
+ : <Overlay show={edit} target={() => ReactDOM.findDOMNode(this.refs.target)} {...this.props}>
+ {popover}
+ </Overlay>
+}
+ </div>
+ );
+ }
+}
+
+Editable.defaultProps = {
+ showButtons: true,
+ inline: false,
+ placement: "top"
+};
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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 (
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {this.props["data-title"]}
+ </Modal.Title>
+ </Modal.Header>
+ );
+ }
+ body() {
+ return (
+ <Modal.Body>
+ {this.props.children}
+ </Modal.Body>
+ );
+ }
+ footer() {
+ return (
+ <Modal.Footer>
+ {
+ this.props.hideCloseBtn
+ ? null
+ : <Button bsStyle='default' onClick={this.cancel.bind(this)} data-stest="cancelbtn">
+ {this.props.closeLabel || this.state.btnCancelText}
+ </Button>
+ }
+ {
+ this.props.hideOkBtn
+ ? null
+ : <Button bsStyle='success' onClick={this.sure.bind(this)} data-stest="okbtn" disabled={this.props.btnOkDisabled}>
+ {this.props.okLabel || this.state.btnOkText}
+ </Button>
+ }
+ </Modal.Footer>
+ );
+ }
+ render() {
+ return (
+ <Modal aria-labelledby='contained-modal-title' backdrop="static" keyboard={true} onHide={this.cancel.bind(this)} show={this.state.show} {...this.props}>
+ {this.props.hideHeader
+ ? ''
+ : this.header()}
+ {this.body()}
+ {this.props.hideFooter
+ ? ''
+ : this.footer()}
+ </Modal>
+ );
+ }
+}
+
+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 (
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {this.state.title}
+ </Modal.Title>
+ </Modal.Header>
+ );
+ }
+ body() {
+ return '';
+ }
+ footer() {
+ return (
+ <Modal.Footer>
+ <Button bsStyle='danger' onClick={this.cancel.bind(this)} data-stest="confirmBoxCancelBtn">
+ {this.state.btnCancelText || 'No'}
+ </Button>
+ <Button bsStyle='success' onClick={this.sure.bind(this)} data-stest="confirmBoxOkBtn">
+ {this.state.btnOkText || 'Yes'}
+ </Button>
+ </Modal.Footer>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSReactToastr.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSReactToastr.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSReactToastr.jsx
new file mode 100644
index 0000000..432ca5b
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/FSReactToastr.jsx
@@ -0,0 +1,37 @@
+/**
+ 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 { render } from 'react-dom';
+import ReactToastr, {ToastMessage, ToastContainer} from "react-toastr";
+import CustomToastContainer from './CustomToastContainer';
+var {animation} = ToastMessage;
+
+var ToastMessageFactory = React.createFactory(animation);
+
+var container = document.createElement('div');
+var body = document.getElementsByTagName('body').item(0);
+body.appendChild(container);
+
+const FSReactToastr = render(
+ <CustomToastContainer
+ toastMessageFactory={ToastMessageFactory}
+ className="toast-top-right" />, container
+);
+
+export default FSReactToastr;
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/contrib/views/storm/src/main/resources/ui/app/scripts/components/Footer.jsx
----------------------------------------------------------------------
diff --git a/contrib/views/storm/src/main/resources/ui/app/scripts/components/Footer.jsx b/contrib/views/storm/src/main/resources/ui/app/scripts/components/Footer.jsx
new file mode 100644
index 0000000..59064d2
--- /dev/null
+++ b/contrib/views/storm/src/main/resources/ui/app/scripts/components/Footer.jsx
@@ -0,0 +1,28 @@
+/**
+ 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 {stormVersion} from '../utils/Constants';
+
+const Footer = () =>{
+ return(
+ <p className="text-center">Apache Storm - {stormVersion}</p>
+ );
+};
+
+export default Footer;
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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(
+ <CommonNotification flag="error" content={result.errorMessage}/>, '', 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(
+ <CommonNotification flag="error" content={result.errorMessage}/>, '', toastOpt);
+ } else {
+ let msg = !!addRow ? "Log configuration added successfully" : (action === 'save' ? "Log configuration applied successfully." : "Log configuration cleared successfully.");
+ FSReactToastr.success(<strong>{msg}</strong>);
+ 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(
+ <div className={`boxAnimated`}>
+ <hr/>
+ <h4 className="col-sm-offset-5">Change Log Level</h4>
+ <p>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.</p>
+ <Table className="table no-margin">
+ <Thead>
+ <Th column="logger" title="Logger">Logger</Th>
+ <Th column="target_level" title="Level">Level</Th>
+ <Th column="timeout" title="Timeout">Timeout</Th>
+ <Th column="timeout_epoch" title="Expires At">Expires At</Th>
+ <Th column="action" title="Action">Action</Th>
+ </Thead>
+ {
+ _.map(_.keys(logLevelObj), (logKey, i) => {
+ return <Tr key={i}>
+ <Td column="logger">
+ <a href="javascript:void(0)">{logKey}</a>
+ </Td>
+ <Td column="target_level">{}
+ <Select value={logLevelObj[logKey].target_level} options={traceOption} onChange={this.traceLavelChange.bind(this,logKey,'target_level',null)} required={true} clearable={false} />
+ </Td>
+ <Td column="timeout">
+ <Editable ref={`logKey${i}`} inline={true} resolve={this.modifyCommonObjValue.bind(this,`logKey${i}`,logKey,'timeout','save',null)} reject={this.modifyCommonObjValue.bind(this,`logKey${i}`,logKey,'timeout','reject',null)}>
+ <input className="form-control input-sm editInput" ref={this.focusInput} defaultValue={logLevelObj[logKey].timeout} onChange={this.handleTimeChange.bind(this)}/>
+ </Editable>
+ </Td>
+ <Td column="timeout_epoch">{this.getDateFormat(logLevelObj[logKey].timeout_epoch)}</Td>
+ <Td column="action">
+ <span>
+ <a href="javascript:void(0)" className="btn btn-success btn-xs" onClick={this.saveAndClearLogConfig.bind(this,logKey,'save')}><i className="fa fa-check"></i></a>
+ <a href="javascript:void(0)" className="btn btn-danger btn-xs" onClick={this.saveAndClearLogConfig.bind(this,logKey,'clear')}><i className="fa fa-times"></i></a>
+ </span>
+ </Td>
+ </Tr>;
+ })
+ }
+ <Tr key={Math.random()}>
+ <Td column="logger">
+ <Editable ref="addRowRef" inline={true} resolve={this.addLoggerName.bind(this,'addRowRef','save')} reject={this.addLoggerName.bind(this,"addRowRef",'reject')}>
+ <input className="form-control input-sm editInput" ref={this.focusInput} defaultValue={selectedKeyName} onChange={this.handleNameChange.bind(this)}/>
+ </Editable>
+ </Td>
+ <Td column="target_level">
+ <Select value={selectedTrace} options={traceOption} onChange={this.traceLavelChange.bind(this,null,'target_level','ADD')} required={true} clearable={false} />
+ </Td>
+ <Td column="timeout">
+ <Editable ref={"timeoutRef"} inline={true} resolve={this.modifyCommonObjValue.bind(this,"timeoutRef",null,'timeout','save','ADD')} reject={this.modifyCommonObjValue.bind(this,"timeoutRef",null,'timeout','reject','ADD')}>
+ <input className="form-control input-sm editInput" ref={this.focusInput} defaultValue={selectedTimeOut} onChange={this.handleTimeChange.bind(this)}/>
+ </Editable>
+ </Td>
+ <Td column="timeout_epoch"> </Td>
+ <Td column="action">
+ <span>
+ <a href="javascript:void(0)" className="btn btn-primary btn-xs" onClick={this.addLogRow.bind(this,'save')}><i className="fa fa-check"></i></a>
+ </span>
+ </Td>
+ </Tr>
+ </Table>
+ </div>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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(
+ <div>
+ <div className={`alert alert-warning alert-dismissible warning-msg ${warnMsg ? '' : 'hidden'}`} role="alert">
+ <strong>Warning!</strong> Please select atleast one worker to perform operation.
+ </div>
+ <div className={`alert alert-success alert-dismissible success-msg ${successMsg ? '' : 'hidden'}`} role="alert">
+ <strong>Success!</strong> Action performed successfully.
+ </div>
+ <div className={`alert alert-danger alert-dismissible error-msg ${errorMsg ? '' : 'hidden'}`} 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.commonBtnAction.bind(this,'dumpjstack')}>JStack</button>
+ <button type="button" className="btn btn-primary" onClick={this.commonBtnAction.bind(this,'restartworker')}>Restart Worker</button>
+ <button type="button" className="btn btn-primary" onClick={this.commonBtnAction.bind(this,'dumpheap')}>Heap</button>
+ </div>
+ </div>
+ <hr />
+ <Table className="table table-bordered" columns={currentPage-1} noDataText="No workers found !">
+ <Thead>
+ <Th column="checkbox">
+ <input type="checkbox" name="single" onChange={this.handleChange.bind(this,null)}/>
+ </Th>
+ <Th column="hostPort" >Host:Port</Th>
+ <Th column="executorId" >Executor Id</Th>
+ </Thead>
+ {
+ _.map(executorArr , (e,i) => {
+ return <Tr key={i}>
+ <Td column="checkbox">
+ <input type="checkbox" checked={e.checked} name="single" onChange={this.handleChange.bind(this,e.hostPort)}/>
+ </Td>
+ <Td column="hostPort">{e.hostPort}</Td>
+ <Td column="executorId">{e.executorId.join(',')}</Td>
+ </Tr>;
+ })
+ }
+ </Table>
+ </div>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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 = "<div class='summary'>" + this.props.labels[0] + ": " + this.props.data[0] + "</div>";
+ text += "<div class='summary'>Free: " + (parseInt(this.props.data[1], 10) - parseInt(this.props.data[0], 10)) + "</div>";
+ text += "<div class='summary'>" + this.props.labels[1] + ": " + this.props.data[1] + "</div>";
+ 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/e3931cc2/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(
+ <CommonNotification flag="error" content={result.errorMessage}/>, '', 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(
+ <div>
+ <div className="form-group row">
+ <div className="col-sm-3">
+ <label>Workers:<span className="text-danger">*</span></label>
+ </div>
+ <div className="col-sm-7">
+ <span style={{float : 'left'}}>0</span><input type="range" title={rebalanceData.workers +' workers selected.'} min={0} style={{width : '90%', float : 'left',margin : '6px 7px' }} max={this.totalWorker} onChange={this.rebalanceValueChange.bind(this,'workers')} /><span style={{float : 'left', clear : 'right'}}>{this.totalWorker}</span>
+ </div>
+ </div>
+ {
+ _.map(spoutArr, (s , i) => {
+ return <div className="form-group row" key={i}>
+ <div className="col-sm-3">
+ <label>{s.spoutId}:<span className="text-danger">*</span></label>
+ </div>
+ <div className="col-sm-7">
+ <input type="number" className="form-control" min={0} defaultValue={s.executors} onChange={this.rebalanceValueChange.bind(this,s.spoutId)} />
+ </div>
+ </div>;
+ })
+ }
+ {
+ _.map(boltArr, (b , n) => {
+ return <div className="form-group row" key={n}>
+ <div className="col-sm-3">
+ <label>{b.boltId}:<span className="text-danger">*</span></label>
+ </div>
+ <div className="col-sm-7">
+ <input type="number" className="form-control" min={0} defaultValue={b.executors} onChange={this.rebalanceValueChange.bind(this,b.boltId)} />
+ </div>
+ </div>;
+ })
+ }
+ <div className="form-group row">
+ <div className="col-sm-3">
+ <label>Wait Time:<span className="text-danger">*</span></label>
+ </div>
+ <div className="col-sm-7">
+ <input type="number" className="form-control" min={0} value={waitTime} onChange={this.waitTimeChange.bind(this)} />
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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 (
+ <div className="col-md-3 pull-right searchbar">
+ <div className="input-group">
+ <input type="text" id="searchBox" className="form-control" placeholder="Search in Logs"/>
+ <div className="input-group-btn">
+ <div className="btn-group" role="group">
+ <div className="dropdown dropdown-lg">
+ <DropdownButton title="" pullRight id="bg-nested-dropdown">
+ <FormGroup>
+ <Checkbox id="searchArchivedLogs">Search archived logs</Checkbox>
+ </FormGroup>
+ <FormGroup>
+ <Checkbox id="deepSearch">Deep search</Checkbox>
+ </FormGroup>
+ </DropdownButton>
+ </div>
+ <button type="button" className="btn btn-default" onClick={this.handleSearch.bind(this)}>
+ <i className="fa fa-search"></i>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ 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/e3931cc2/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 = "<ul>";
+ if (d[":capacity"] !== null){ tip += "<li>Capacity: " + d[":capacity"].toFixed(2) + "</li>";}
+ if (d[":latency"] !== null){ tip += "<li>Latency: " + d[":latency"].toFixed(2) + "</li>";}
+ if (d[":transferred"] !== null){ tip += "<li>Transferred: " + d[":transferred"].toFixed(2) + "</li>";}
+ tip += "</ul>";
+ 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 (
+ <svg className="topology-graph" width="100%" height="300"></svg>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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 (
+ <div className="container-fluid">
+ {this.props.children}
+ <Confirm ref="Confirm" onKeyUp={this.handleKeyPress}/>
+ <Footer />
+ </div>
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e3931cc2/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(
+ <CommonNotification flag="error" content={result.errorMessage}/>, '', toastOpt);
+ } else {
+ this.setState({entity : result});
+ }
+ });
+ }
+ render(){
+ const {entity} = this.state;
+ return(
+ <div>
+ <div className="row">
+ <div className="col-sm-6">
+ <OverlayTrigger placement="bottom" overlay={<Tooltip id="tooltip1">Executors are threads in a Worker process.</Tooltip>}>
+ <div className="tile primary">
+ <div className="tile-header">Executor</div>
+ <div className="tile-body">
+ <i className="fa fa-play-circle-o"></i>
+ <span className="count">{entity.executorsTotal}</span>
+ </div>
+ </div>
+ </OverlayTrigger>
+ </div>
+ <div className="col-sm-6">
+ <OverlayTrigger placement="bottom" overlay={<Tooltip id="tooltip1">A Task is an instance of a Bolt or Spout. The number of Tasks is almost always equal to the number of Executors.</Tooltip>}>
+ <div className="tile warning">
+ <div className="tile-header">Tasks</div>
+ <div className="tile-body">
+ <i className="fa fa-tasks"></i>
+ <span className="count">{entity.tasksTotal}</span>
+ </div>
+ </div>
+ </OverlayTrigger>
+ </div>
+ </div>
+ <div className="row">
+ <div className="col-sm-6">
+ <OverlayTrigger placement="bottom" overlay={<Tooltip id="tooltip1">The number of nodes in the cluster currently.</Tooltip>}>
+ <div className="tile success">
+ <div className="tile-header" style={{textAlign:"center"}}>Supervisor</div>
+ <div className="tile-body" style={{textAlign:"center"}}>
+ <div id="supervisorCount">
+ <RadialChart
+ data={[entity.supervisors,entity.supervisors]}
+ labels={['Used','Total']}
+ width={100}
+ height={100}
+ innerRadius={46}
+ outerRadius={50}
+ color={["rgba(255,255,255,0.6)", "rgba(255,255,255,1)"]}
+ />
+ </div>
+ </div>
+ </div>
+ </OverlayTrigger>
+ </div>
+ <div className="col-sm-6">
+ <OverlayTrigger placement="bottom" overlay={<Tooltip id="tooltip1">Slots are Workers (processes).</Tooltip>}>
+ <div className="tile danger">
+ <div className="tile-header" style={{textAlign:"center"}}>Slots</div>
+ <div className="tile-body" style={{textAlign:"center"}}>
+ <div id="slotsCount">
+ <RadialChart
+ data={[entity.slotsUsed,entity.slotsTotal]}
+ labels={['Used','Total']}
+ width={100}
+ height={100}
+ innerRadius={46}
+ outerRadius={50}
+ color={["rgba(255,255,255,0.6)", "rgba(255,255,255,1)"]}
+ />
+ </div>
+ </div>
+ </div>
+ </OverlayTrigger>
+ </div>
+ </div>
+ <div className="row">
+ <div className="col-sm-12">
+ <NimbusSummary fromDashboard={true}/>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}