forked from p15670423/monkey
UI: Replace startOverPage with an improved Island reset modal
New modal allows to save clicks, explains the situation better, offers to export the config and allows deleting agent data without deleting config
This commit is contained in:
parent
551439dcc2
commit
75034f37f6
|
@ -6,7 +6,6 @@ import ConfigurePage from './pages/ConfigurePage.js';
|
|||
import RunMonkeyPage from './pages/RunMonkeyPage/RunMonkeyPage';
|
||||
import MapPage from './pages/MapPage';
|
||||
import TelemetryPage from './pages/TelemetryPage';
|
||||
import StartOverPage from './pages/ResetPage';
|
||||
import ReportPage from './pages/ReportPage';
|
||||
import LicensePage from './pages/LicensePage';
|
||||
import AuthComponent from './AuthComponent';
|
||||
|
@ -47,7 +46,6 @@ export const Routes = {
|
|||
RunMonkeyPage: '/run-monkey',
|
||||
MapPage: '/infection/map',
|
||||
TelemetryPage: '/infection/telemetry',
|
||||
StartOverPage: '/start-over',
|
||||
LicensePage: '/license'
|
||||
}
|
||||
|
||||
|
@ -232,8 +230,6 @@ class AppComponent extends AuthComponent {
|
|||
<SidebarLayoutComponent component={MapPage} {...defaultSideNavProps}/>)}
|
||||
{this.renderRoute(Routes.TelemetryPage,
|
||||
<SidebarLayoutComponent component={TelemetryPage} {...defaultSideNavProps}/>)}
|
||||
{this.renderRoute(Routes.StartOverPage,
|
||||
<SidebarLayoutComponent component={StartOverPage} {...defaultSideNavProps}/>)}
|
||||
{this.redirectToReport()}
|
||||
{this.renderRoute(Routes.SecurityReport,
|
||||
<SidebarLayoutComponent component={ReportPage}
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
import React, {ReactFragment} from 'react';
|
||||
import React, {ReactFragment, useState} from 'react';
|
||||
import {Button} from 'react-bootstrap';
|
||||
import {NavLink} from 'react-router-dom';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
|
||||
import '../styles/components/SideNav.scss';
|
||||
import {CompletedSteps} from "./side-menu/CompletedSteps";
|
||||
import {isReportRoute, Routes} from "./Main";
|
||||
import {CompletedSteps} from './side-menu/CompletedSteps';
|
||||
import {isReportRoute, Routes} from './Main';
|
||||
import Logo from './logo/LogoComponent';
|
||||
import IslandResetModal from './ui-components/IslandResetModal';
|
||||
|
||||
|
||||
const logoImage = require('../images/monkey-icon.svg');
|
||||
const infectionMonkeyImage = require('../images/infection-monkey.svg');
|
||||
|
||||
import Logo from "./logo/LogoComponent";
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean,
|
||||
completedSteps: CompletedSteps,
|
||||
defaultReport: string,
|
||||
header?: ReactFragment
|
||||
header?: ReactFragment,
|
||||
onStatusChange: () => void
|
||||
}
|
||||
|
||||
|
||||
const SideNavComponent = ({disabled,
|
||||
completedSteps,
|
||||
defaultReport,
|
||||
header=null}: Props) => {
|
||||
const SideNavComponent = ({
|
||||
disabled,
|
||||
completedSteps,
|
||||
defaultReport,
|
||||
header = null,
|
||||
onStatusChange,
|
||||
}: Props) => {
|
||||
|
||||
const [showResetModal, setShowResetModal] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -37,12 +44,12 @@ const SideNavComponent = ({disabled,
|
|||
|
||||
<ul className='navigation'>
|
||||
{(header !== null) &&
|
||||
<>
|
||||
<li>
|
||||
{header}
|
||||
</li>
|
||||
<hr/>
|
||||
</>}
|
||||
<>
|
||||
<li>
|
||||
{header}
|
||||
</li>
|
||||
<hr/>
|
||||
</>}
|
||||
|
||||
<li>
|
||||
<NavLink to={Routes.RunMonkeyPage} className={getNavLinkClass()}>
|
||||
|
@ -76,10 +83,17 @@ const SideNavComponent = ({disabled,
|
|||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to={Routes.StartOverPage} className={getNavLinkClass()}>
|
||||
<Button variant={null} className={'island-reset-button'}
|
||||
onClick={() => setShowResetModal(true)} href='#'>
|
||||
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
|
||||
Reset
|
||||
</NavLink>
|
||||
</Button>
|
||||
<IslandResetModal show={showResetModal}
|
||||
allMonkeysAreDead={areMonkeysDead()}
|
||||
onClose={() => {
|
||||
setShowResetModal(false);
|
||||
onStatusChange();
|
||||
}}/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -90,7 +104,7 @@ const SideNavComponent = ({disabled,
|
|||
Configuration
|
||||
</NavLink></li>
|
||||
<li><NavLink to='/infection/telemetry'
|
||||
className={getNavLinkClass()}>
|
||||
className={getNavLinkClass()}>
|
||||
Telemetries
|
||||
</NavLink></li>
|
||||
</ul>
|
||||
|
@ -98,8 +112,12 @@ const SideNavComponent = ({disabled,
|
|||
<Logo/>
|
||||
</>);
|
||||
|
||||
function areMonkeysDead() {
|
||||
return (!completedSteps['runMonkey']) || (completedSteps['infectionDone'])
|
||||
}
|
||||
|
||||
function getNavLinkClass() {
|
||||
if(disabled){
|
||||
if (disabled) {
|
||||
return `nav-link disabled`
|
||||
} else {
|
||||
return ''
|
||||
|
|
|
@ -9,6 +9,7 @@ const SidebarLayoutComponent = ({component: Component,
|
|||
completedSteps = null,
|
||||
defaultReport = '',
|
||||
sideNavHeader = (<></>),
|
||||
onStatusChange = () => {},
|
||||
...other
|
||||
}) => (
|
||||
<Route {...other} render={() => {
|
||||
|
@ -18,9 +19,10 @@ const SidebarLayoutComponent = ({component: Component,
|
|||
<SideNavComponent disabled={sideNavDisabled}
|
||||
completedSteps={completedSteps}
|
||||
defaultReport={defaultReport}
|
||||
header={sideNavHeader}/>
|
||||
header={sideNavHeader}
|
||||
onStatusChange={onStatusChange}/>
|
||||
</Col>}
|
||||
<Component {...other} />
|
||||
<Component onStatusChange={onStatusChange} {...other} />
|
||||
</Row>)
|
||||
}}/>
|
||||
)
|
||||
|
|
|
@ -12,8 +12,11 @@ import Logo from "../logo/LogoComponent";
|
|||
const monkeyIcon = require('../../images/monkey-icon.svg')
|
||||
const infectionMonkey = require('../../images/infection-monkey.svg')
|
||||
|
||||
const LandingPageComponent = (props) => {
|
||||
type Props = {
|
||||
onStatusChange: () => void
|
||||
}
|
||||
|
||||
const LandingPageComponent = (props: Props) => {
|
||||
return (
|
||||
<>
|
||||
<ParticleBackground/>
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
import React from 'react';
|
||||
import {Col, Button} from 'react-bootstrap';
|
||||
import {Link} from 'react-router-dom';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import IslandResetModal from '../ui-components/IslandResetModal';
|
||||
import '../../styles/pages/StartOverPage.scss';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
|
||||
class ResetPageComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
cleaned: false,
|
||||
showCleanDialog: false,
|
||||
allMonkeysAreDead: false
|
||||
};
|
||||
|
||||
this.cleanup = this.cleanup.bind(this);
|
||||
this.closeModal = this.closeModal.bind(this);
|
||||
}
|
||||
|
||||
updateMonkeysRunning = () => {
|
||||
this.authFetch('/api')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
// This check is used to prevent unnecessary re-rendering
|
||||
this.setState({
|
||||
allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done'])
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Col sm={{offset: 3, span: 9}} md={{offset: 3, span: 9}}
|
||||
lg={{offset: 3, span: 9}} xl={{offset: 2, span: 7}}
|
||||
className={'main'}>
|
||||
<IslandResetModal cleaned={this.state.cleaned}
|
||||
showCleanDialog={this.state.showCleanDialog}
|
||||
allMonkeysAreDead={this.state.allMonkeysAreDead}
|
||||
onVerify={this.cleanup}
|
||||
onClose={this.closeModal}/>
|
||||
<h1 className="page-title">Reset</h1>
|
||||
<div style={{'fontSize': '1.2em'}}>
|
||||
<p>
|
||||
If you are finished and want to start over with a fresh configuration, erase the logs and clear the map
|
||||
you can go ahead and
|
||||
</p>
|
||||
<p style={{margin: '20px'}} className={'text-center'}>
|
||||
<Button className="btn btn-danger btn-lg center-block"
|
||||
onClick={() => {
|
||||
this.setState({showCleanDialog: true});
|
||||
this.updateMonkeysRunning();
|
||||
}
|
||||
}>
|
||||
Reset the Environment
|
||||
</Button>
|
||||
</p>
|
||||
<div className="alert alert-info">
|
||||
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
|
||||
You don't have to reset the environment to keep running monkeys.
|
||||
You can continue and <Link to="/run-monkey">Run More Monkeys</Link> as you wish,
|
||||
and see the results on the <Link to="/infection/map">Infection Map</Link> without deleting anything.
|
||||
</div>
|
||||
{this.state.cleaned ?
|
||||
<div className="alert alert-success">
|
||||
<FontAwesomeIcon icon={faCheck} style={{'marginRight': '5px'}}/>
|
||||
Environment was reset successfully
|
||||
</div>
|
||||
: ''}
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
cleanup = () => {
|
||||
this.setState({
|
||||
cleaned: false
|
||||
});
|
||||
return this.authFetch('/api?action=reset')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res['status'] === 'OK') {
|
||||
this.setState({
|
||||
cleaned: true
|
||||
});
|
||||
}
|
||||
}).then(() => {
|
||||
this.updateMonkeysRunning();
|
||||
this.props.onStatusChange();
|
||||
});
|
||||
};
|
||||
|
||||
closeModal = () => {
|
||||
this.setState({
|
||||
showCleanDialog: false
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export default ResetPageComponent;
|
|
@ -1,76 +0,0 @@
|
|||
import {Modal} from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
import {GridLoader} from 'react-spinners';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
|
||||
|
||||
|
||||
class IslandResetModal extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showCleanDialog: this.props.showCleanDialog,
|
||||
allMonkeysAreDead: this.props.allMonkeysAreDead,
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props !== prevProps) {
|
||||
this.setState({ showCleanDialog: this.props.showCleanDialog,
|
||||
allMonkeysAreDead: this.props.allMonkeysAreDead})
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<Modal show={this.state.showCleanDialog} onHide={() => this.props.onClose()}>
|
||||
<Modal.Body>
|
||||
<h2>
|
||||
<div className='text-center'>Reset environment</div>
|
||||
</h2>
|
||||
<p style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
|
||||
Are you sure you want to reset the environment?
|
||||
</p>
|
||||
{
|
||||
!this.state.allMonkeysAreDead ?
|
||||
<div className='alert alert-warning'>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
Some monkeys are still running. It's advised to kill all monkeys before resetting.
|
||||
</div>
|
||||
:
|
||||
<div/>
|
||||
}
|
||||
{
|
||||
this.state.loading ? <div className={'modalLoader'}><GridLoader/></div> : this.showModalButtons()
|
||||
}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
};
|
||||
|
||||
showModalButtons() {
|
||||
return (<div className='text-center'>
|
||||
<button type='button' className='btn btn-danger btn-lg' style={{margin: '5px'}}
|
||||
onClick={this.modalVerificationOnClick}>
|
||||
Reset environment
|
||||
</button>
|
||||
<button type='button' className='btn btn-success btn-lg' style={{margin: '5px'}}
|
||||
onClick={() => {this.props.onClose(); this.setState({showCleanDialog: false})}}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>)
|
||||
}
|
||||
|
||||
modalVerificationOnClick = async () => {
|
||||
this.setState({loading: true});
|
||||
this.props.onVerify()
|
||||
.then(() => {this.setState({loading: false});
|
||||
this.props.onClose();})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default IslandResetModal;
|
|
@ -0,0 +1,130 @@
|
|||
import {Button, Col, Container, Modal, NavLink, Row} from 'react-bootstrap';
|
||||
import React, {useState} from 'react';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
|
||||
|
||||
import '../../styles/components/IslandResetModal.scss';
|
||||
import {Routes} from '../Main';
|
||||
import LoadingIcon from './LoadingIcon';
|
||||
import {faCheck} from '@fortawesome/free-solid-svg-icons';
|
||||
import AuthService from '../../services/AuthService';
|
||||
|
||||
type Props = {
|
||||
show: boolean,
|
||||
allMonkeysAreDead: boolean,
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
// Button statuses
|
||||
const Idle = 1;
|
||||
const Loading = 2;
|
||||
const Done = 3;
|
||||
|
||||
const IslandResetModal = (props: Props) => {
|
||||
|
||||
const [resetAllStatus, setResetAll] = useState(Idle);
|
||||
const [deleteStatus, setDeleteStatus] = useState(Idle);
|
||||
const auth = new AuthService();
|
||||
|
||||
return (
|
||||
<Modal show={props.show} onHide={() => {
|
||||
setDeleteStatus(Idle);
|
||||
props.onClose()
|
||||
}} size={'lg'}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Reset the Island</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{
|
||||
!props.allMonkeysAreDead ?
|
||||
<div className='alert alert-warning'>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} style={{'marginRight': '5px'}}/>
|
||||
Can't reset the Island while Monkey agents are still running!
|
||||
</div>
|
||||
:
|
||||
showModalButtons()
|
||||
}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
function displayDeleteData() {
|
||||
if (deleteStatus === Idle) {
|
||||
return (
|
||||
<button type='button' className='btn btn-danger btn-lg' style={{margin: '5px'}}
|
||||
onClick={() => {
|
||||
setDeleteStatus(Loading);
|
||||
resetIsland('/api?action=delete-agent-data',
|
||||
() => {
|
||||
setDeleteStatus(Done)
|
||||
})
|
||||
}}>
|
||||
Delete data
|
||||
</button>
|
||||
)
|
||||
} else if (deleteStatus === Loading) {
|
||||
return (<LoadingIcon/>)
|
||||
} else if (deleteStatus === Done) {
|
||||
return (<FontAwesomeIcon icon={faCheck} className={'status-success'} size={'2x'}/>)
|
||||
}
|
||||
}
|
||||
|
||||
function displayResetAll() {
|
||||
if (resetAllStatus === Idle) {
|
||||
return (
|
||||
<button type='button' className='btn btn-danger btn-lg' style={{margin: '5px'}}
|
||||
onClick={() => {
|
||||
setResetAll(Loading);
|
||||
resetIsland('/api?action=reset',
|
||||
() => {
|
||||
setResetAll(Done);
|
||||
props.onClose();
|
||||
})
|
||||
}}>
|
||||
Reset the Island
|
||||
</button>
|
||||
)
|
||||
} else if (resetAllStatus === Loading) {
|
||||
return (<LoadingIcon/>)
|
||||
} else if (resetAllStatus === Done) {
|
||||
return (<FontAwesomeIcon icon={faCheck} className={'status-success'} size={'2x'}/>)
|
||||
}
|
||||
}
|
||||
|
||||
function resetIsland(url: string, callback: () => void) {
|
||||
auth.authFetch(url)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res['status'] === 'OK') {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showModalButtons() {
|
||||
return (<Container className={`text-left island-reset-modal`}>
|
||||
<Row>
|
||||
<Col>
|
||||
<p>Delete data gathered by Monkey agents.</p>
|
||||
<p>This will reset the Map and reports.</p>
|
||||
</Col>
|
||||
<Col sm={4} className={'text-center'}>
|
||||
{displayDeleteData()}
|
||||
</Col>
|
||||
</Row>
|
||||
<hr/>
|
||||
<Row>
|
||||
<Col>
|
||||
<p>Reset everything.</p>
|
||||
<p>You might want to <Button variant={'link'} href={Routes.ConfigurePage}>export
|
||||
configuration</Button> before doing this.</p>
|
||||
</Col>
|
||||
<Col sm={4} className={'text-center'}>
|
||||
{displayResetAll()}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>)
|
||||
}
|
||||
}
|
||||
|
||||
export default IslandResetModal;
|
|
@ -0,0 +1,9 @@
|
|||
.island-reset-modal p {
|
||||
font-size: 16.5px;
|
||||
margin: 0;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.island-reset-modal a {
|
||||
padding: 0 0 4px 0;
|
||||
}
|
|
@ -1,6 +1,13 @@
|
|||
.sidebar .version-text {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar .license-link {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navigation .island-reset-button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
$yellow: #ffcc00;
|
||||
|
||||
.modalLoader div{
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.modalLoader div>div{
|
||||
background-color: $yellow;
|
||||
}
|
Loading…
Reference in New Issue