forked from p15670423/monkey
Merge pull request #867 from VakarisZ/aws_run_option_fix
Run on AWS instance option fix
This commit is contained in:
commit
d8440303ce
|
@ -54,16 +54,15 @@ See [Amazon's documentation about working with SSM agents](https://docs.aws.amaz
|
||||||
|
|
||||||
### Running the monkey
|
### Running the monkey
|
||||||
|
|
||||||
When you run the monkey island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
|
When you run the Monkey Island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
|
||||||
|
|
||||||
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-1.png "Running a Monkey on EC2 Instance")
|
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-1.png "Running a Monkey on EC2 Instance")
|
||||||
|
|
||||||
And then you can choose one of the available instances as "patient zero" like so:
|
After you click on "AWS run" you can choose one of the available instances as "patient zero" like so:
|
||||||
|
|
||||||
1. Click on "Run on AWS"
|
1. Choose the relevant Network Interface
|
||||||
2. Choose the relevant Network Interface
|
2. Select the machines you'd like to run the Monkey on
|
||||||
3. Select the machines you'd like to run the Monkey on
|
3. Click "Run on Selected Machines", and watch the monkey go! 🐒
|
||||||
4. Click "Run on Selected Machines", and watch the monkey go! 🐒
|
|
||||||
|
|
||||||
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-2.png "Running a Monkey on EC2 Instance")
|
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-2.png "Running a Monkey on EC2 Instance")
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 72 KiB |
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 97 KiB |
|
@ -35,7 +35,8 @@
|
||||||
"comma-dangle": 1,
|
"comma-dangle": 1,
|
||||||
"quotes": [
|
"quotes": [
|
||||||
1,
|
1,
|
||||||
"single"
|
"single",
|
||||||
|
{"allowTemplateLiterals": true}
|
||||||
],
|
],
|
||||||
"no-undef": 1,
|
"no-undef": 1,
|
||||||
"global-strict": 0,
|
"global-strict": 0,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
|
||||||
import DropdownSelect from '../../ui-components/DropdownSelect';
|
import DropdownSelect from '../../../ui-components/DropdownSelect';
|
||||||
import {OS_TYPES} from './OsTypes';
|
import {OS_TYPES} from '../utils/OsTypes';
|
||||||
import GenerateLocalWindowsCmd from './commands/local_windows_cmd';
|
import GenerateLocalWindowsCmd from '../commands/local_windows_cmd';
|
||||||
import GenerateLocalWindowsPowershell from './commands/local_windows_powershell';
|
import GenerateLocalWindowsPowershell from '../commands/local_windows_powershell';
|
||||||
import GenerateLocalLinuxWget from './commands/local_linux_wget';
|
import GenerateLocalLinuxWget from '../commands/local_linux_wget';
|
||||||
import GenerateLocalLinuxCurl from './commands/local_linux_curl';
|
import GenerateLocalLinuxCurl from '../commands/local_linux_curl';
|
||||||
import CommandDisplay from './CommandDisplay';
|
import CommandDisplay from '../utils/CommandDisplay';
|
||||||
|
|
||||||
|
|
||||||
const LocalManualRunOptions = (props) => {
|
const LocalManualRunOptions = (props) => {
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import ReactTable from 'react-table'
|
||||||
|
import checkboxHOC from 'react-table/lib/hoc/selectTable';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
||||||
|
const CheckboxTable = checkboxHOC(ReactTable);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
Header: 'Machines',
|
||||||
|
columns: [
|
||||||
|
{Header: 'Machine', accessor: 'name'},
|
||||||
|
{Header: 'Instance ID', accessor: 'instance_id'},
|
||||||
|
{Header: 'IP Address', accessor: 'ip_address'},
|
||||||
|
{Header: 'OS', accessor: 'os'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
function AWSInstanceTable(props) {
|
||||||
|
|
||||||
|
const [allToggled, setAllToggled] = useState(false);
|
||||||
|
let checkboxTable = null;
|
||||||
|
|
||||||
|
function toggleSelection(key) {
|
||||||
|
key = key.replace('select-', '');
|
||||||
|
// start off with the existing state
|
||||||
|
let modifiedSelection = [...props.selection];
|
||||||
|
const keyIndex = modifiedSelection.indexOf(key);
|
||||||
|
// check to see if the key exists
|
||||||
|
if (keyIndex >= 0) {
|
||||||
|
// it does exist so we will remove it using destructing
|
||||||
|
modifiedSelection = [
|
||||||
|
...modifiedSelection.slice(0, keyIndex),
|
||||||
|
...modifiedSelection.slice(keyIndex + 1)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// it does not exist so add it
|
||||||
|
modifiedSelection.push(key);
|
||||||
|
}
|
||||||
|
// update the state
|
||||||
|
props.setSelection(modifiedSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(key) {
|
||||||
|
return props.selection.includes(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAll() {
|
||||||
|
const selectAll = !allToggled;
|
||||||
|
const selection = [];
|
||||||
|
if (selectAll) {
|
||||||
|
// we need to get at the internals of ReactTable
|
||||||
|
const wrappedInstance = checkboxTable.getWrappedInstance();
|
||||||
|
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||||
|
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||||
|
// we just push all the IDs onto the selection array
|
||||||
|
currentRecords.forEach(item => {
|
||||||
|
selection.push(item._original.instance_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setAllToggled(selectAll);
|
||||||
|
props.setSelection(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrProps(_, r) {
|
||||||
|
let color = 'inherit';
|
||||||
|
if (r) {
|
||||||
|
let instId = r.original.instance_id;
|
||||||
|
if (isSelected(instId)) {
|
||||||
|
color = '#ffed9f';
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(props.results, instId)) {
|
||||||
|
color = props.results[instId] ? '#00f01b' : '#f00000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
style: {backgroundColor: color}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<CheckboxTable
|
||||||
|
ref={r => (checkboxTable = r)}
|
||||||
|
keyField="instance_id"
|
||||||
|
columns={columns}
|
||||||
|
data={props.data}
|
||||||
|
showPagination={true}
|
||||||
|
defaultPageSize={pageSize}
|
||||||
|
className="-highlight"
|
||||||
|
selectType="checkbox"
|
||||||
|
toggleSelection={toggleSelection}
|
||||||
|
isSelected={isSelected}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
selectAll={allToggled}
|
||||||
|
getTrProps={getTrProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AWSInstanceTable.propTypes = {
|
||||||
|
data: PropTypes.arrayOf(PropTypes.exact({
|
||||||
|
instance_id: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
os: PropTypes.string,
|
||||||
|
ip_address: PropTypes.string
|
||||||
|
})),
|
||||||
|
results: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
selection: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
setSelection: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AWSInstanceTable;
|
|
@ -0,0 +1,80 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import AuthComponent from '../../../AuthComponent';
|
||||||
|
import '../../../../styles/components/RunOnIslandButton.scss';
|
||||||
|
import {faCloud} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import AWSRunOptions from './AWSRunOptions';
|
||||||
|
import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton';
|
||||||
|
import {Alert, Button} from 'react-bootstrap';
|
||||||
|
import LoadingIcon from '../../../ui-components/LoadingIcon';
|
||||||
|
|
||||||
|
|
||||||
|
function AWSRunButton(props) {
|
||||||
|
|
||||||
|
const authComponent = new AuthComponent({});
|
||||||
|
|
||||||
|
const [isOnAWS, setIsOnAWS] = useState(false);
|
||||||
|
const [AWSInstances, setAWSInstances] = useState([]);
|
||||||
|
const [awsMachineCollectionError, setAwsMachineCollectionError] = useState('');
|
||||||
|
const [componentLoading, setComponentLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkIsOnAWS();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function checkIsOnAWS() {
|
||||||
|
authComponent.authFetch('/api/remote-monkey?action=list_aws')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let isAws = res['is_aws'];
|
||||||
|
setComponentLoading(false);
|
||||||
|
if (isAws) {
|
||||||
|
// On AWS!
|
||||||
|
// Checks if there was an error while collecting the aws machines.
|
||||||
|
let isErrorWhileCollectingAwsMachines = (res['error'] != null);
|
||||||
|
if (isErrorWhileCollectingAwsMachines) {
|
||||||
|
// There was an error. Finish loading, and display error message.
|
||||||
|
setIsOnAWS(true);
|
||||||
|
setAwsMachineCollectionError(res['error']);
|
||||||
|
} else {
|
||||||
|
// No error! Finish loading and display machines for user
|
||||||
|
setIsOnAWS(true);
|
||||||
|
setAWSInstances(res['instances']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAWSButton() {
|
||||||
|
return <NextSelectionButton title={'AWS run'}
|
||||||
|
description={'Run on a chosen AWS instance in the cloud.'}
|
||||||
|
icon={faCloud}
|
||||||
|
onButtonClick={() => {
|
||||||
|
props.setComponent(AWSRunOptions,
|
||||||
|
{AWSInstances: AWSInstances, setComponent: props.setComponent})
|
||||||
|
}}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorDisplay() {
|
||||||
|
return (
|
||||||
|
<Alert variant={'info'}>Detected ability to run on different AWS instances.
|
||||||
|
To enable this feature, follow the
|
||||||
|
<Button variant={'link'} className={'inline-link'}
|
||||||
|
href={'https://www.guardicore.com/infectionmonkey/docs/usage/integrations/aws-run-on-ec2-machine/'}>
|
||||||
|
Tutorial
|
||||||
|
</Button> and refresh the page. Error received while trying to list AWS instances: {awsMachineCollectionError}
|
||||||
|
</Alert> );
|
||||||
|
}
|
||||||
|
|
||||||
|
let displayed = '';
|
||||||
|
if (componentLoading) {
|
||||||
|
displayed = LoadingIcon();
|
||||||
|
}
|
||||||
|
if (awsMachineCollectionError !== '') {
|
||||||
|
displayed = getErrorDisplay();
|
||||||
|
} else if (isOnAWS) {
|
||||||
|
displayed = getAWSButton();
|
||||||
|
}
|
||||||
|
return displayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AWSRunButton;
|
|
@ -0,0 +1,117 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {Button, Nav} from 'react-bootstrap';
|
||||||
|
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
|
||||||
|
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
|
||||||
|
import AwsRunTable from './AWSInstanceTable';
|
||||||
|
import AuthComponent from '../../../AuthComponent';
|
||||||
|
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
|
||||||
|
|
||||||
|
|
||||||
|
const AWSRunOptions = (props) => {
|
||||||
|
return InlineSelection(getContents, {
|
||||||
|
...props,
|
||||||
|
onBackButtonClick: () => {props.setComponent()}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const getContents = (props) => {
|
||||||
|
|
||||||
|
const authComponent = new AuthComponent({});
|
||||||
|
|
||||||
|
let [allIPs, setAllIPs] = useState([]);
|
||||||
|
let [selectedIp, setSelectedIp] = useState(null);
|
||||||
|
let [AWSClicked, setAWSClicked] = useState(false);
|
||||||
|
let [runResults, setRunResults] = useState([]);
|
||||||
|
let [selectedInstances, setSelectedInstances] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getIps();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function getIps() {
|
||||||
|
authComponent.authFetch('/api')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
setAllIPs(res['ip_addresses']);
|
||||||
|
setSelectedIp(res['ip_addresses'][0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runOnAws() {
|
||||||
|
setAWSClicked(true);
|
||||||
|
let instances = selectedInstances.map(x => instanceIdToInstance(x));
|
||||||
|
|
||||||
|
authComponent.authFetch('/api/remote-monkey',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({type: 'aws', instances: instances, island_ip: selectedIp})
|
||||||
|
}).then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let result = res['result'];
|
||||||
|
|
||||||
|
// update existing state, not run-over
|
||||||
|
let prevRes = result;
|
||||||
|
for (let key in result) {
|
||||||
|
if (result.hasOwnProperty(key)) {
|
||||||
|
prevRes[key] = result[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setRunResults(prevRes);
|
||||||
|
setSelectedInstances([]);
|
||||||
|
setAWSClicked(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function instanceIdToInstance(instance_id) {
|
||||||
|
let instance = props.AWSInstances.find(
|
||||||
|
function (inst) {
|
||||||
|
return inst['instance_id'] === instance_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {'instance_id': instance_id, 'os': instance['os']}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{'marginBottom': '2em'}}>
|
||||||
|
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
|
||||||
|
<p className="alert alert-info">
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
|
||||||
|
Not sure what this is? Not seeing your AWS EC2 instances? <a
|
||||||
|
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances"
|
||||||
|
rel="noopener noreferrer" target="_blank">Read the documentation</a>!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
allIPs.length > 1 ?
|
||||||
|
<Nav variant="pills" activeKey={selectedIp} onSelect={setSelectedIp}
|
||||||
|
style={{'marginBottom': '2em'}}>
|
||||||
|
{allIPs.map(ip => <Nav.Item key={ip}><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
|
||||||
|
</Nav>
|
||||||
|
: <div style={{'marginBottom': '2em'}}/>
|
||||||
|
}
|
||||||
|
<AwsRunTable
|
||||||
|
data={props.AWSInstances}
|
||||||
|
results={runResults}
|
||||||
|
selection={selectedInstances}
|
||||||
|
setSelection={setSelectedInstances}
|
||||||
|
/>
|
||||||
|
<div className={'aws-run-button-container'}>
|
||||||
|
<Button
|
||||||
|
size={'lg'}
|
||||||
|
onClick={runOnAws}
|
||||||
|
className={'btn btn-default btn-md center-block'}
|
||||||
|
disabled={AWSClicked}>
|
||||||
|
Run on selected machines
|
||||||
|
{AWSClicked ?
|
||||||
|
<FontAwesomeIcon icon={faSync} className={`text-success spinning-icon`} style={{'marginLeft': '5px'}}/> : null}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AWSRunOptions;
|
|
@ -1,12 +1,13 @@
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
|
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
|
||||||
import LocalManualRunOptions from './LocalManualRunOptions';
|
import LocalManualRunOptions from './RunManually/LocalManualRunOptions';
|
||||||
import AuthComponent from '../../AuthComponent';
|
import AuthComponent from '../../AuthComponent';
|
||||||
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
|
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
|
||||||
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
||||||
import {cloneDeep} from 'lodash';
|
import {cloneDeep} from 'lodash';
|
||||||
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
|
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
|
||||||
import RunOnIslandButton from './RunOnIslandButton';
|
import RunOnIslandButton from './RunOnIslandButton';
|
||||||
|
import AWSRunButton from './RunOnAWS/AWSRunButton';
|
||||||
|
|
||||||
function RunOptions(props) {
|
function RunOptions(props) {
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@ function RunOptions(props) {
|
||||||
setComponent(LocalManualRunOptions,
|
setComponent(LocalManualRunOptions,
|
||||||
{ips: ips, setComponent: setComponent})
|
{ips: ips, setComponent: setComponent})
|
||||||
}}/>
|
}}/>
|
||||||
|
<AWSRunButton setComponent={setComponent}/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {OS_TYPES} from '../OsTypes';
|
import {OS_TYPES} from '../utils/OsTypes';
|
||||||
|
|
||||||
|
|
||||||
export default function generateLocalLinuxCurl(ip, osType) {
|
export default function generateLocalLinuxCurl(ip, osType) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {OS_TYPES} from '../OsTypes';
|
import {OS_TYPES} from '../utils/OsTypes';
|
||||||
|
|
||||||
|
|
||||||
export default function generateLocalLinuxWget(ip, osType) {
|
export default function generateLocalLinuxWget(ip, osType) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {OS_TYPES} from '../OsTypes';
|
import {OS_TYPES} from '../utils/OsTypes';
|
||||||
|
|
||||||
|
|
||||||
export default function generateLocalWindowsCmd(ip, osType) {
|
export default function generateLocalWindowsCmd(ip, osType) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {OS_TYPES} from '../OsTypes';
|
import {OS_TYPES} from '../utils/OsTypes';
|
||||||
|
|
||||||
|
|
||||||
export default function generateLocalWindowsPowershell(ip, osType) {
|
export default function generateLocalWindowsPowershell(ip, osType) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
|
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
|
||||||
import LocalManualRunOptions from './LocalManualRunOptions';
|
import LocalManualRunOptions from '../RunManually/LocalManualRunOptions';
|
||||||
|
|
||||||
function InterfaceSelection(props) {
|
function InterfaceSelection(props) {
|
||||||
return InlineSelection(getContents, props, LocalManualRunOptions)
|
return InlineSelection(getContents, props, LocalManualRunOptions)
|
|
@ -1,109 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ReactTable from 'react-table'
|
|
||||||
import checkboxHOC from 'react-table/lib/hoc/selectTable';
|
|
||||||
|
|
||||||
const CheckboxTable = checkboxHOC(ReactTable);
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
Header: 'Machines',
|
|
||||||
columns: [
|
|
||||||
{Header: 'Machine', accessor: 'name'},
|
|
||||||
{Header: 'Instance ID', accessor: 'instance_id'},
|
|
||||||
{Header: 'IP Address', accessor: 'ip_address'},
|
|
||||||
{Header: 'OS', accessor: 'os'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const pageSize = 10;
|
|
||||||
|
|
||||||
class AwsRunTableComponent extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
selection: [],
|
|
||||||
selectAll: false,
|
|
||||||
result: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSelection = (key) => {
|
|
||||||
// start off with the existing state
|
|
||||||
let selection = [...this.state.selection];
|
|
||||||
const keyIndex = selection.indexOf(key);
|
|
||||||
// check to see if the key exists
|
|
||||||
if (keyIndex >= 0) {
|
|
||||||
// it does exist so we will remove it using destructing
|
|
||||||
selection = [
|
|
||||||
...selection.slice(0, keyIndex),
|
|
||||||
...selection.slice(keyIndex + 1)
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
// it does not exist so add it
|
|
||||||
selection.push(key);
|
|
||||||
}
|
|
||||||
// update the state
|
|
||||||
this.setState({selection});
|
|
||||||
};
|
|
||||||
|
|
||||||
isSelected = key => {
|
|
||||||
return this.state.selection.includes(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleAll = () => {
|
|
||||||
const selectAll = !this.state.selectAll;
|
|
||||||
const selection = [];
|
|
||||||
if (selectAll) {
|
|
||||||
// we need to get at the internals of ReactTable
|
|
||||||
const wrappedInstance = this.checkboxTable.getWrappedInstance();
|
|
||||||
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
|
||||||
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
|
||||||
// we just push all the IDs onto the selection array
|
|
||||||
currentRecords.forEach(item => {
|
|
||||||
selection.push(item._original.instance_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setState({selectAll, selection});
|
|
||||||
};
|
|
||||||
|
|
||||||
getTrProps = (_, r) => {
|
|
||||||
let color = 'inherit';
|
|
||||||
if (r) {
|
|
||||||
let instId = r.original.instance_id;
|
|
||||||
if (this.isSelected(instId)) {
|
|
||||||
color = '#ffed9f';
|
|
||||||
} else if (Object.prototype.hasOwnProperty.call(this.state.result, instId)) {
|
|
||||||
color = this.state.result[instId] ? '#00f01b' : '#f00000'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
style: {backgroundColor: color}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="data-table-container">
|
|
||||||
<CheckboxTable
|
|
||||||
ref={r => (this.checkboxTable = r)}
|
|
||||||
keyField="instance_id"
|
|
||||||
columns={columns}
|
|
||||||
data={this.props.data}
|
|
||||||
showPagination={true}
|
|
||||||
defaultPageSize={pageSize}
|
|
||||||
className="-highlight"
|
|
||||||
selectType="checkbox"
|
|
||||||
toggleSelection={this.toggleSelection}
|
|
||||||
isSelected={this.isSelected}
|
|
||||||
toggleAll={this.toggleAll}
|
|
||||||
selectAll={this.state.selectAll}
|
|
||||||
getTrProps={this.getTrProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AwsRunTableComponent;
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faSync} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function LoadingIcon() {
|
||||||
|
return <FontAwesomeIcon icon={faSync} className={`spinning-icon loading-icon`} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadingIcon;
|
|
@ -17,6 +17,7 @@
|
||||||
@import 'components/inline-selection/BackButton';
|
@import 'components/inline-selection/BackButton';
|
||||||
@import 'components/inline-selection/CommandDisplay';
|
@import 'components/inline-selection/CommandDisplay';
|
||||||
@import 'components/Icons';
|
@import 'components/Icons';
|
||||||
|
@import 'components/Buttons';
|
||||||
|
|
||||||
|
|
||||||
// Define custom elements after bootstrap import
|
// Define custom elements after bootstrap import
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
a.inline-link {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
|
@ -1,3 +1,10 @@
|
||||||
|
.loading-icon {
|
||||||
|
color: $monkey-yellow;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-left: 50%;
|
||||||
|
margin-right: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.spinning-icon {
|
.spinning-icon {
|
||||||
animation: spin-animation 0.5s infinite;
|
animation: spin-animation 0.5s infinite;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -14,3 +14,8 @@ div.run-on-os-buttons > .nav-item > .nav-link:hover{
|
||||||
color: $monkey-white;
|
color: $monkey-white;
|
||||||
background-color: $monkey-alt;
|
background-color: $monkey-alt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aws-run-button-container {
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ module.exports = {
|
||||||
publicPath: '/'
|
publicPath: '/'
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
|
host: '0.0.0.0',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://localhost:5000',
|
target: 'https://localhost:5000',
|
||||||
|
|
Loading…
Reference in New Issue