forked from p15670423/monkey
Merge pull request #1909 from guardicore/957-island-reset-improvements
957 island reset improvements
This commit is contained in:
commit
b9efc2d552
|
@ -16,6 +16,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- The ability to download the Monkey Island logs from the Infection Map page. #1640
|
- The ability to download the Monkey Island logs from the Infection Map page. #1640
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Reset workflow. Now it's possible to delete data gathered by agents without
|
||||||
|
resetting the configuration and reset procedure requires fewer clicks. #957
|
||||||
- "Communicate as Backdoor User" PBA's HTTP requests to request headers only and
|
- "Communicate as Backdoor User" PBA's HTTP requests to request headers only and
|
||||||
include a timeout. #1577
|
include a timeout. #1577
|
||||||
- The setup procedure for custom server_config.json files to be simpler. #1576
|
- The setup procedure for custom server_config.json files to be simpler. #1576
|
||||||
|
|
|
@ -20,9 +20,8 @@ Choosing the "Custom" scenario will allow you to fine-tune your simulation and a
|
||||||
|
|
||||||
![Choose scenario](/images/usage/scenarios/choose-scenario.png "Choose a scenario")
|
![Choose scenario](/images/usage/scenarios/choose-scenario.png "Choose a scenario")
|
||||||
|
|
||||||
To exit a scenario and select another one, click on "Start Over".
|
To exit a scenario and select another one, click on "Reset".
|
||||||
|
![Reset](/images/usage/scenarios/reset.jpg "Reset")
|
||||||
![Start over](/images/usage/scenarios/start-over.png "Start over")
|
|
||||||
|
|
||||||
## Section contents
|
## Section contents
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
Binary file not shown.
Before Width: | Height: | Size: 104 KiB |
|
@ -2,6 +2,9 @@ from mongoengine import BooleanField, EmbeddedDocument
|
||||||
|
|
||||||
|
|
||||||
class Config(EmbeddedDocument):
|
class Config(EmbeddedDocument):
|
||||||
|
|
||||||
|
COLLECTION_NAME = "config"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
No need to define this schema here. It will change often and is already is defined in
|
No need to define this schema here. It will change often and is already is defined in
|
||||||
monkey_island.cc.services.config_schema.
|
monkey_island.cc.services.config_schema.
|
||||||
|
|
|
@ -2,4 +2,6 @@ from mongoengine import Document, StringField
|
||||||
|
|
||||||
|
|
||||||
class IslandMode(Document):
|
class IslandMode(Document):
|
||||||
|
COLLECTION_NAME = "island_mode"
|
||||||
|
|
||||||
mode = StringField()
|
mode = StringField()
|
||||||
|
|
|
@ -19,6 +19,8 @@ class Root(flask_restful.Resource):
|
||||||
|
|
||||||
if not action:
|
if not action:
|
||||||
return self.get_server_info()
|
return self.get_server_info()
|
||||||
|
elif action == "delete-agent-data":
|
||||||
|
return jwt_required(Database.reset_db)(reset_config=False)
|
||||||
elif action == "reset":
|
elif action == "reset":
|
||||||
return jwt_required(Database.reset_db)()
|
return jwt_required(Database.reset_db)()
|
||||||
elif action == "is-up":
|
elif action == "is-up":
|
||||||
|
|
|
@ -3,8 +3,10 @@ import logging
|
||||||
from flask import jsonify
|
from flask import jsonify
|
||||||
|
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
|
from monkey_island.cc.models import Config
|
||||||
from monkey_island.cc.models.agent_controls import AgentControls
|
from monkey_island.cc.models.agent_controls import AgentControls
|
||||||
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
||||||
|
from monkey_island.cc.models.island_mode_model import IslandMode
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -15,19 +17,29 @@ class Database(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_db():
|
def reset_db(reset_config=True):
|
||||||
logger.info("Resetting database")
|
logger.info("Resetting database")
|
||||||
# We can't drop system collections.
|
# We can't drop system collections.
|
||||||
[
|
[
|
||||||
Database.drop_collection(x)
|
Database.drop_collection(x)
|
||||||
for x in mongo.db.collection_names()
|
for x in mongo.db.collection_names()
|
||||||
if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME
|
if Database._should_drop(x, reset_config)
|
||||||
]
|
]
|
||||||
ConfigService.init_config()
|
ConfigService.init_config()
|
||||||
Database.init_agent_controls()
|
Database.init_agent_controls()
|
||||||
logger.info("DB was reset")
|
logger.info("DB was reset")
|
||||||
return jsonify(status="OK")
|
return jsonify(status="OK")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _should_drop(collection: str, drop_config: bool) -> bool:
|
||||||
|
if not drop_config:
|
||||||
|
if collection == IslandMode.COLLECTION_NAME or collection == Config.COLLECTION_NAME:
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
not collection.startswith("system.")
|
||||||
|
and not collection == AttackMitigations.COLLECTION_NAME
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def drop_collection(collection_name: str):
|
def drop_collection(collection_name: str):
|
||||||
mongo.db[collection_name].drop()
|
mongo.db[collection_name].drop()
|
||||||
|
|
|
@ -6,7 +6,6 @@ import ConfigurePage from './pages/ConfigurePage.js';
|
||||||
import RunMonkeyPage from './pages/RunMonkeyPage/RunMonkeyPage';
|
import RunMonkeyPage from './pages/RunMonkeyPage/RunMonkeyPage';
|
||||||
import MapPage from './pages/MapPage';
|
import MapPage from './pages/MapPage';
|
||||||
import TelemetryPage from './pages/TelemetryPage';
|
import TelemetryPage from './pages/TelemetryPage';
|
||||||
import StartOverPage from './pages/StartOverPage';
|
|
||||||
import ReportPage from './pages/ReportPage';
|
import ReportPage from './pages/ReportPage';
|
||||||
import LicensePage from './pages/LicensePage';
|
import LicensePage from './pages/LicensePage';
|
||||||
import AuthComponent from './AuthComponent';
|
import AuthComponent from './AuthComponent';
|
||||||
|
@ -47,7 +46,6 @@ export const Routes = {
|
||||||
RunMonkeyPage: '/run-monkey',
|
RunMonkeyPage: '/run-monkey',
|
||||||
MapPage: '/infection/map',
|
MapPage: '/infection/map',
|
||||||
TelemetryPage: '/infection/telemetry',
|
TelemetryPage: '/infection/telemetry',
|
||||||
StartOverPage: '/start-over',
|
|
||||||
LicensePage: '/license'
|
LicensePage: '/license'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,8 +230,6 @@ class AppComponent extends AuthComponent {
|
||||||
<SidebarLayoutComponent component={MapPage} {...defaultSideNavProps}/>)}
|
<SidebarLayoutComponent component={MapPage} {...defaultSideNavProps}/>)}
|
||||||
{this.renderRoute(Routes.TelemetryPage,
|
{this.renderRoute(Routes.TelemetryPage,
|
||||||
<SidebarLayoutComponent component={TelemetryPage} {...defaultSideNavProps}/>)}
|
<SidebarLayoutComponent component={TelemetryPage} {...defaultSideNavProps}/>)}
|
||||||
{this.renderRoute(Routes.StartOverPage,
|
|
||||||
<SidebarLayoutComponent component={StartOverPage} {...defaultSideNavProps}/>)}
|
|
||||||
{this.redirectToReport()}
|
{this.redirectToReport()}
|
||||||
{this.renderRoute(Routes.SecurityReport,
|
{this.renderRoute(Routes.SecurityReport,
|
||||||
<SidebarLayoutComponent component={ReportPage}
|
<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 {NavLink} from 'react-router-dom';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||||
import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
|
import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo';
|
||||||
import '../styles/components/SideNav.scss';
|
import '../styles/components/SideNav.scss';
|
||||||
import {CompletedSteps} from "./side-menu/CompletedSteps";
|
import {CompletedSteps} from './side-menu/CompletedSteps';
|
||||||
import {isReportRoute, Routes} from "./Main";
|
import {isReportRoute, Routes} from './Main';
|
||||||
|
import Logo from './logo/LogoComponent';
|
||||||
|
import IslandResetModal from './ui-components/IslandResetModal';
|
||||||
|
|
||||||
|
|
||||||
const logoImage = require('../images/monkey-icon.svg');
|
const logoImage = require('../images/monkey-icon.svg');
|
||||||
const infectionMonkeyImage = require('../images/infection-monkey.svg');
|
const infectionMonkeyImage = require('../images/infection-monkey.svg');
|
||||||
|
|
||||||
import Logo from "./logo/LogoComponent";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
completedSteps: CompletedSteps,
|
completedSteps: CompletedSteps,
|
||||||
defaultReport: string,
|
defaultReport: string,
|
||||||
header?: ReactFragment
|
header?: ReactFragment,
|
||||||
|
onStatusChange: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const SideNavComponent = ({disabled,
|
const SideNavComponent = ({
|
||||||
|
disabled,
|
||||||
completedSteps,
|
completedSteps,
|
||||||
defaultReport,
|
defaultReport,
|
||||||
header=null}: Props) => {
|
header = null,
|
||||||
|
onStatusChange,
|
||||||
|
}: Props) => {
|
||||||
|
|
||||||
|
const [showResetModal, setShowResetModal] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -76,10 +83,17 @@ const SideNavComponent = ({disabled,
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<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>
|
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
|
||||||
Start Over
|
Reset
|
||||||
</NavLink>
|
</Button>
|
||||||
|
<IslandResetModal show={showResetModal}
|
||||||
|
allMonkeysAreDead={areMonkeysDead()}
|
||||||
|
onClose={() => {
|
||||||
|
setShowResetModal(false);
|
||||||
|
onStatusChange();
|
||||||
|
}}/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -98,8 +112,12 @@ const SideNavComponent = ({disabled,
|
||||||
<Logo/>
|
<Logo/>
|
||||||
</>);
|
</>);
|
||||||
|
|
||||||
|
function areMonkeysDead() {
|
||||||
|
return (!completedSteps['runMonkey']) || (completedSteps['infectionDone'])
|
||||||
|
}
|
||||||
|
|
||||||
function getNavLinkClass() {
|
function getNavLinkClass() {
|
||||||
if(disabled){
|
if (disabled) {
|
||||||
return `nav-link disabled`
|
return `nav-link disabled`
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -9,6 +9,7 @@ const SidebarLayoutComponent = ({component: Component,
|
||||||
completedSteps = null,
|
completedSteps = null,
|
||||||
defaultReport = '',
|
defaultReport = '',
|
||||||
sideNavHeader = (<></>),
|
sideNavHeader = (<></>),
|
||||||
|
onStatusChange = () => {},
|
||||||
...other
|
...other
|
||||||
}) => (
|
}) => (
|
||||||
<Route {...other} render={() => {
|
<Route {...other} render={() => {
|
||||||
|
@ -18,9 +19,10 @@ const SidebarLayoutComponent = ({component: Component,
|
||||||
<SideNavComponent disabled={sideNavDisabled}
|
<SideNavComponent disabled={sideNavDisabled}
|
||||||
completedSteps={completedSteps}
|
completedSteps={completedSteps}
|
||||||
defaultReport={defaultReport}
|
defaultReport={defaultReport}
|
||||||
header={sideNavHeader}/>
|
header={sideNavHeader}
|
||||||
|
onStatusChange={onStatusChange}/>
|
||||||
</Col>}
|
</Col>}
|
||||||
<Component {...other} />
|
<Component onStatusChange={onStatusChange} {...other} />
|
||||||
</Row>)
|
</Row>)
|
||||||
}}/>
|
}}/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,8 +12,11 @@ import Logo from "../logo/LogoComponent";
|
||||||
const monkeyIcon = require('../../images/monkey-icon.svg')
|
const monkeyIcon = require('../../images/monkey-icon.svg')
|
||||||
const infectionMonkey = require('../../images/infection-monkey.svg')
|
const infectionMonkey = require('../../images/infection-monkey.svg')
|
||||||
|
|
||||||
const LandingPageComponent = (props) => {
|
type Props = {
|
||||||
|
onStatusChange: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const LandingPageComponent = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ParticleBackground/>
|
<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 StartOverModal from '../ui-components/StartOverModal';
|
|
||||||
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 StartOverPageComponent 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'}>
|
|
||||||
<StartOverModal cleaned={this.state.cleaned}
|
|
||||||
showCleanDialog={this.state.showCleanDialog}
|
|
||||||
allMonkeysAreDead={this.state.allMonkeysAreDead}
|
|
||||||
onVerify={this.cleanup}
|
|
||||||
onClose={this.closeModal}/>
|
|
||||||
<h1 className="page-title">Start Over</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 StartOverPageComponent;
|
|
|
@ -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'}}/>
|
||||||
|
Please stop all running agents before attempting to reset the Island.
|
||||||
|
</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;
|
|
@ -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 StartOverModal 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 StartOverModal;
|
|
|
@ -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 {
|
.sidebar .version-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .license-link {
|
.sidebar .license-link {
|
||||||
position: relative;
|
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