Merge branch 'run_page_ui_improvements' into 519/scoutsuite-integration

This commit is contained in:
VakarisZ 2020-09-23 14:58:51 +03:00
commit a7fc5d1191
9 changed files with 133 additions and 93 deletions

View File

@ -90,7 +90,7 @@ script:
- cd monkey_island/cc/ui - cd monkey_island/cc/ui
- npm ci # See https://docs.npmjs.com/cli/ci.html - npm ci # See https://docs.npmjs.com/cli/ci.html
- eslint ./src --quiet # Test for errors - eslint ./src --quiet # Test for errors
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=8 - JS_WARNINGS_AMOUNT_UPPER_LIMIT=7
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
# Build documentation # Build documentation

View File

@ -51,7 +51,7 @@ def run_local_monkey():
logger.error('popen failed', exc_info=True) logger.error('popen failed', exc_info=True)
return False, "popen failed: %s" % exc return False, "popen failed: %s" % exc
return True, "pis: %s" % pid return True, ""
class LocalRun(flask_restful.Resource): class LocalRun(flask_restful.Resource):

View File

@ -43,7 +43,7 @@ export default function commandDisplay(props) {
{renderNav()} {renderNav()}
<Card> <Card>
<div style={{'overflow': 'auto', 'padding': '0.5em'}}> <div style={{'overflow': 'auto', 'padding': '0.5em'}}>
<CopyToClipboard text={selectedCommand.type} className="pull-right btn-sm"> <CopyToClipboard text={selectedCommand.command} className="pull-right btn-sm">
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard"> <Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
<FontAwesomeIcon icon={faClipboard}/> <FontAwesomeIcon icon={faClipboard}/>
</Button> </Button>

View File

@ -8,7 +8,7 @@ function InterfaceSelection(props) {
const getContents = (props) => { const getContents = (props) => {
const ips = props.ips.map((ip) => const ips = props.ips.map((ip) =>
<div>{ip}</div> <div key={ip}>{ip}</div>
); );
return (<div>{ips}</div>); return (<div>{ips}</div>);
} }

View File

@ -49,7 +49,7 @@ const getContents = (props) => {
return ( return (
<> <>
<DropdownSelect defaultKey={'win64'} options={osTypes} onClick={setOsType} variant={'outline-monkey'}/> <DropdownSelect defaultKey={OS_TYPES.WINDOWS_64} options={osTypes} onClick={setOsType} variant={'outline-monkey'}/>
<DropdownSelect defaultKey={0} options={props.ips} onClick={setIp} variant={'outline-monkey'}/> <DropdownSelect defaultKey={0} options={props.ips} onClick={setIp} variant={'outline-monkey'}/>
<CommandDisplay commands={commands}/> <CommandDisplay commands={commands}/>
</> </>

View File

@ -6,16 +6,24 @@ import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync'; import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
import AuthComponent from '../../AuthComponent'; import AuthComponent from '../../AuthComponent';
import MissingBinariesModal from '../../ui-components/MissingBinariesModal'; import IslandMonkeyRunErrorModal from '../../ui-components/IslandMonkeyRunErrorModal';
import '../../../styles/components/RunOnIslandButton.scss'; import '../../../styles/components/RunOnIslandButton.scss';
import {faTimes} from '@fortawesome/free-solid-svg-icons';
const MONKEY_STATES = {
RUNNING: 'running',
NOT_RUNNING: 'not_running',
STARTING: 'starting',
FAILED: 'failed'
}
class RunOnIslandButton extends AuthComponent { class RunOnIslandButton extends AuthComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
runningOnIslandState: 'not_running', runningOnIslandState: MONKEY_STATES.NOT_RUNNING,
showModal: false, showModal: false,
errorDetails: '' errorDetails: ''
}; };
@ -28,19 +36,19 @@ class RunOnIslandButton extends AuthComponent {
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
if (res['is_running']) { if (res['is_running']) {
this.setState({runningOnIslandState: 'running'}); this.setState({runningOnIslandState: MONKEY_STATES.RUNNING});
} else { } else {
this.setState({runningOnIslandState: 'not_running'}); this.setState({runningOnIslandState: MONKEY_STATES.NOT_RUNNING});
} }
}); });
} }
runIslandMonkey = () => { runIslandMonkey = () => {
this.setState({runningOnIslandState: 'installing'}, this.sendRunMonkeyRequest) this.setState({runningOnIslandState: MONKEY_STATES.STARTING}, this.sendRunMonkeyRequest)
}; };
sendRunMonkeyRequest = () => { sendRunMonkeyRequest() {
this.authFetch('/api/local-monkey', this.authFetch('/api/local-monkey',
{ {
method: 'POST', method: 'POST',
@ -48,23 +56,22 @@ class RunOnIslandButton extends AuthComponent {
body: JSON.stringify({action: 'run'}) body: JSON.stringify({action: 'run'})
}) })
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(async res => {
if (res['is_running']) { if (res['is_running']) {
await new Promise(r => setTimeout(r, 1000));
this.setState({ this.setState({
runningOnIslandState: 'running' runningOnIslandState: MONKEY_STATES.RUNNING
}); });
} else { } else {
/* If Monkey binaries are missing, change the state accordingly */ /* If Monkey binaries are missing, change the state accordingly */
if (res['error_text'].startsWith('Copy file failed')) { if (res['error_text'] !== '') {
this.setState({ this.setState({
showModal: true, showModal: true,
errorDetails: res['error_text'] errorDetails: res['error_text'],
runningOnIslandState: MONKEY_STATES.FAILED
} }
); );
} }
this.setState({
runningOnIslandState: 'not_running'
});
} }
}); });
} }
@ -76,12 +83,15 @@ class RunOnIslandButton extends AuthComponent {
}; };
getMonkeyRunStateIcon = () => { getMonkeyRunStateIcon = () => {
if (this.state.runningOnIslandState === 'running') { if (this.state.runningOnIslandState === MONKEY_STATES.RUNNING) {
return (<FontAwesomeIcon icon={faCheck} return (<FontAwesomeIcon icon={faCheck}
className={`monkey-on-island-run-state-icon text-success`}/>) className={`monkey-on-island-run-state-icon text-success`}/>)
} else if (this.state.runningOnIslandState === 'installing') { } else if (this.state.runningOnIslandState === MONKEY_STATES.STARTING) {
return (<FontAwesomeIcon icon={faSync} return (<FontAwesomeIcon icon={faSync}
className={`monkey-on-island-run-state-icon text-success spinning-icon`}/>) className={`monkey-on-island-run-state-icon text-success spinning-icon`}/>)
} else if (this.state.runningOnIslandState === MONKEY_STATES.FAILED) {
return (<FontAwesomeIcon icon={faTimes}
className={`monkey-on-island-run-state-icon text-danger`}/>)
} else { } else {
return ''; return '';
} }
@ -93,7 +103,7 @@ class RunOnIslandButton extends AuthComponent {
return ( return (
<Row> <Row>
<Col> <Col>
<MissingBinariesModal <IslandMonkeyRunErrorModal
showModal={this.state.showModal} showModal={this.state.showModal}
onClose={this.closeModal} onClose={this.closeModal}
errorDetails={this.state.errorDetails}/> errorDetails={this.state.errorDetails}/>

View File

@ -16,19 +16,11 @@ export default function DropdownSelect(props) {
} }
function generateDropdownItemsFromArray(data) { function generateDropdownItemsFromArray(data) {
const dropdownItems = []; return data.map((x, i) => generateDropdownItem(i, x));
for (let i = 0; i < data.length; i++) {
dropdownItems.push(generateDropdownItem(i, data[i]));
}
return dropdownItems;
} }
function generateDropdownItemsFromObject(data) { function generateDropdownItemsFromObject(data) {
const dropdownItems = []; return Object.entries(data).map(([key, value]) => generateDropdownItem(key, value));
for (let [key, value] of Object.entries(data)) {
dropdownItems.push(generateDropdownItem(key, value));
}
return dropdownItems;
} }
function generateDropdownItem(key, value) { function generateDropdownItem(key, value) {

View File

@ -0,0 +1,100 @@
import {Modal} from 'react-bootstrap';
import React from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
class IslandMonkeyRunErrorModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
showModal: this.props.showModal,
errorDetails: this.props.errorDetails
};
}
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
this.setState({
showModal: this.props.showModal,
errorDetails: this.props.errorDetails
})
}
}
getMissingBinariesContent() {
return (
<div>
Some Monkey binaries are not found where they should be...<br/>
You can download the files from <a href="https://github.com/guardicore/monkey/releases/latest"
target="blank">here</a>,
at the bottommost section titled "Assets", and place them under the
directory <code>monkey/monkey_island/cc/binaries</code>.
</div>
)
}
getMonkeyAlreadyRunningContent() {
return (
<div>
Most likely, monkey is already running on the Island. Wait until it finishes or kill the process to run again.
</div>
)
}
getUndefinedErrorContent() {
return (
<div>
You encountered an undefined error. Please report it to support@infectionmonkey.com or our slack channel.
</div>
)
}
getDisplayContentByError(errorMsg) {
if (errorMsg.includes('Permission denied:')) {
return this.getMonkeyAlreadyRunningContent()
} else if (errorMsg.startsWith('Copy file failed')) {
return this.getMissingBinariesContent()
} else {
return this.getUndefinedErrorContent()
}
}
render = () => {
return (
<Modal show={this.state.showModal} onHide={() => this.props.onClose()}>
<Modal.Body>
<h3>
<div className='text-center'>Uh oh...</div>
</h3>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-warning">
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
{this.getDisplayContentByError(this.state.errorDetails)}
</p>
</div>
<hr/>
<h4>
Error Details
</h4>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<pre>
{this.state.errorDetails}
</pre>
</div>
<div className='text-center'>
<button type='button' className='btn btn-success btn-lg' style={{margin: '5px'}}
onClick={() => this.props.onClose()}>
Dismiss
</button>
</div>
</Modal.Body>
</Modal>
)
};
}
export default IslandMonkeyRunErrorModal;

View File

@ -1,62 +0,0 @@
import {Modal} from 'react-bootstrap';
import React from 'react';
class MissingBinariesModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
showModal: this.props.showModal,
errorDetails: this.props.errorDetails
};
}
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
this.setState({
showModal: this.props.showModal,
errorDetails: this.props.errorDetails
})
}
}
render = () => {
return (
<Modal show={this.state.showModal} onHide={() => this.props.onClose()}>
<Modal.Body>
<h3>
<div className='text-center'>Uh oh...</div>
</h3>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-warning">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
Some Monkey binaries are not found where they should be...<br/>
You can download the files from <a href="https://github.com/guardicore/monkey/releases/latest" target="blank">here</a>,
at the bottommost section titled "Assets", and place them under the directory <code>monkey/monkey_island/cc/binaries</code>.
</p>
</div>
<hr/>
<h4>
Error Details
</h4>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<pre>
{this.state.errorDetails}
</pre>
</div>
<div className='text-center'>
<button type='button' className='btn btn-success btn-lg' style={{margin: '5px'}}
onClick={() => this.props.onClose()}>
Dismiss
</button>
</div>
</Modal.Body>
</Modal>
)
};
}
export default MissingBinariesModal;