forked from p15670423/monkey
PBA file refactoring almost working
This commit is contained in:
parent
6cc4c85132
commit
aad9e5069e
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
"server_config": "password",
|
"server_config": "password",
|
||||||
"deployment": "develop"
|
"deployment": "develop",
|
||||||
}
|
"user": "test",
|
||||||
|
"password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React from 'react';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
|
||||||
|
import {FilePond} from 'react-filepond';
|
||||||
|
import 'filepond/dist/filepond.min.css';
|
||||||
|
|
||||||
|
|
||||||
|
class PbaInput extends AuthComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
console.log("Constructor called");
|
||||||
|
// set schema from server
|
||||||
|
this.state = this.getStateFromProps(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateFromProps(props){
|
||||||
|
let options = props.options
|
||||||
|
// set schema from server
|
||||||
|
return {
|
||||||
|
PBAFile: options.PbaFile,
|
||||||
|
filename: options.filename,
|
||||||
|
apiEndpoint: options.apiEndpoint,
|
||||||
|
setPbaFile: options.setPbaFile
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getPBAfile() {
|
||||||
|
if (this.state.PBAFile.length !== 0) {
|
||||||
|
return this.state.PBAFile
|
||||||
|
return PbaInput.getMockPBAfile(this.state.PBAFile)
|
||||||
|
} else if (this.state.filename) {
|
||||||
|
return PbaInput.getFullPBAfile(this.state.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMockPBAfile(mockFile) {
|
||||||
|
let pbaFile = [{
|
||||||
|
name: mockFile.name,
|
||||||
|
source: mockFile.name,
|
||||||
|
options: {
|
||||||
|
type: 'limbo'
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
pbaFile[0].options.file = mockFile;
|
||||||
|
return pbaFile
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static getFullPBAfile(filename) {
|
||||||
|
return [{
|
||||||
|
source: filename,
|
||||||
|
options: {
|
||||||
|
type: 'limbo'
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerParams(path) {
|
||||||
|
return {
|
||||||
|
url: path,
|
||||||
|
process: this.getRequestParams(),
|
||||||
|
revert: this.getRequestParams(),
|
||||||
|
restore: this.getRequestParams(),
|
||||||
|
load: this.getRequestParams(),
|
||||||
|
fetch: this.getRequestParams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequestParams() {
|
||||||
|
return {headers: {'Authorization': this.jwtHeader}}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<FilePond
|
||||||
|
key={this.state.apiEndpoint}
|
||||||
|
server={this.getServerParams(this.state.apiEndpoint)}
|
||||||
|
files={this.getPBAfile()}
|
||||||
|
onupdatefiles={fileItems => {
|
||||||
|
if (fileItems.length > 0) {
|
||||||
|
this.state.setPbaFile([fileItems[0].file], fileItems[0].file.name)
|
||||||
|
} else {
|
||||||
|
this.state.setPbaFile([], "")
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default PbaInput;
|
|
@ -0,0 +1,70 @@
|
||||||
|
import ArrayFieldTemplate from "../ui-components/AdvancedMultipleSelect";
|
||||||
|
import PbaInput from "./PbaInput";
|
||||||
|
import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage';
|
||||||
|
|
||||||
|
export default function UiSchema(props) {
|
||||||
|
const UiSchema = {
|
||||||
|
basic: {
|
||||||
|
'ui:order': ['general', 'credentials'],
|
||||||
|
credentials: {
|
||||||
|
exploit_password_list: {
|
||||||
|
"ui:ArrayFieldTemplate": ArrayFieldTemplate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
basic_network: {},
|
||||||
|
monkey: {
|
||||||
|
behaviour: {
|
||||||
|
custom_PBA_linux_cmd: {
|
||||||
|
'ui:widget': 'textarea',
|
||||||
|
'ui:emptyValue': ''
|
||||||
|
},
|
||||||
|
PBA_linux_file: {
|
||||||
|
'ui:widget': PbaInput,
|
||||||
|
'ui:options': {
|
||||||
|
PbaFile: props.configuration.PBAlinuxFile,
|
||||||
|
filename: props.configuration.configuration.monkey.behaviour.PBA_linux_filename,
|
||||||
|
apiEndpoint: API_PBA_LINUX,
|
||||||
|
setPbaFile: props.setPbaFileLinux
|
||||||
|
}
|
||||||
|
},
|
||||||
|
custom_PBA_windows_cmd: {
|
||||||
|
'ui:widget': 'textarea',
|
||||||
|
'ui:emptyValue': ''
|
||||||
|
},
|
||||||
|
PBA_windows_file: {
|
||||||
|
'ui:widget': PbaInput,
|
||||||
|
'ui:options': {
|
||||||
|
PbaFile: props.configuration.PBAwindowsFile,
|
||||||
|
filename: props.configuration.configuration.monkey.behaviour.PBA_windows_filename,
|
||||||
|
apiEndpoint: API_PBA_WINDOWS,
|
||||||
|
setPbaFile: props.setPbaFileWindows
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PBA_linux_filename: {
|
||||||
|
classNames: 'linux-pba-file-info',
|
||||||
|
'ui:emptyValue': ''
|
||||||
|
},
|
||||||
|
PBA_windows_filename: {
|
||||||
|
classNames: 'windows-pba-file-info',
|
||||||
|
'ui:emptyValue': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cnc: {},
|
||||||
|
network: {},
|
||||||
|
exploits: {
|
||||||
|
general: {
|
||||||
|
exploiter_classes: {
|
||||||
|
"ui:ArrayFieldTemplate": ArrayFieldTemplate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
internal: {
|
||||||
|
general: {
|
||||||
|
started_on_island: {'ui:widget': 'hidden'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UiSchema[props.configuration.selectedSection]
|
||||||
|
}
|
|
@ -3,9 +3,8 @@ import Form from 'react-jsonschema-form-bs4';
|
||||||
import {Col, Modal, Nav, Button} from 'react-bootstrap';
|
import {Col, Modal, Nav, Button} from 'react-bootstrap';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import {FilePond} from 'react-filepond';
|
|
||||||
import 'filepond/dist/filepond.min.css';
|
|
||||||
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
|
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
|
||||||
|
import UiSchema from '../configuration-components/UiSchema';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
|
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
|
||||||
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||||
|
@ -13,20 +12,19 @@ import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamati
|
||||||
|
|
||||||
const ATTACK_URL = '/api/attack';
|
const ATTACK_URL = '/api/attack';
|
||||||
const CONFIG_URL = '/api/configuration/island';
|
const CONFIG_URL = '/api/configuration/island';
|
||||||
|
export const API_PBA_LINUX = '/api/fileUpload/PBAlinux';
|
||||||
|
export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows';
|
||||||
|
|
||||||
class ConfigurePageComponent extends AuthComponent {
|
class ConfigurePageComponent extends AuthComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.PBAwindowsPond = null;
|
|
||||||
this.PBAlinuxPond = null;
|
|
||||||
this.currentSection = 'attack';
|
this.currentSection = 'attack';
|
||||||
this.currentFormData = {};
|
this.currentFormData = {};
|
||||||
this.initialConfig = {};
|
this.initialConfig = {};
|
||||||
this.initialAttackConfig = {};
|
this.initialAttackConfig = {};
|
||||||
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
|
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
|
||||||
this.uiSchemas = this.getUiSchemas();
|
|
||||||
// set schema from server
|
|
||||||
this.state = {
|
this.state = {
|
||||||
schema: {},
|
schema: {},
|
||||||
configuration: {},
|
configuration: {},
|
||||||
|
@ -34,51 +32,11 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
lastAction: 'none',
|
lastAction: 'none',
|
||||||
sections: [],
|
sections: [],
|
||||||
selectedSection: 'attack',
|
selectedSection: 'attack',
|
||||||
PBAwinFile: [],
|
showAttackAlert: false,
|
||||||
PBAlinuxFile: [],
|
|
||||||
showAttackAlert: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getUiSchemas() {
|
PBAwindowsFile: [],
|
||||||
return ({
|
PBAlinuxFile: []
|
||||||
basic: {'ui:order': ['general', 'credentials']},
|
};
|
||||||
basic_network: {},
|
|
||||||
monkey: {
|
|
||||||
behaviour: {
|
|
||||||
custom_PBA_linux_cmd: {
|
|
||||||
'ui:widget': 'textarea',
|
|
||||||
'ui:emptyValue': ''
|
|
||||||
},
|
|
||||||
PBA_linux_file: {
|
|
||||||
'ui:widget': this.PBAlinux
|
|
||||||
},
|
|
||||||
custom_PBA_windows_cmd: {
|
|
||||||
'ui:widget': 'textarea',
|
|
||||||
'ui:emptyValue': ''
|
|
||||||
},
|
|
||||||
PBA_windows_file: {
|
|
||||||
'ui:widget': this.PBAwindows
|
|
||||||
},
|
|
||||||
PBA_linux_filename: {
|
|
||||||
classNames: 'linux-pba-file-info',
|
|
||||||
'ui:emptyValue': ''
|
|
||||||
},
|
|
||||||
PBA_windows_filename: {
|
|
||||||
classNames: 'windows-pba-file-info',
|
|
||||||
'ui:emptyValue': ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cnc: {},
|
|
||||||
network: {},
|
|
||||||
exploits: {},
|
|
||||||
internal: {
|
|
||||||
general: {
|
|
||||||
started_on_island: {'ui:widget': 'hidden'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialConfig(config) {
|
setInitialConfig(config) {
|
||||||
|
@ -122,7 +80,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.setInitialConfig(data.configuration);
|
this.setInitialConfig(data.configuration);
|
||||||
this.setState({configuration: data.configuration})
|
this.setState({configuration: data.configuration});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -271,7 +229,6 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
resetConfig = () => {
|
resetConfig = () => {
|
||||||
this.removePBAfiles();
|
|
||||||
this.authFetch(CONFIG_URL,
|
this.authFetch(CONFIG_URL,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -280,14 +237,15 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
lastAction: 'reset',
|
lastAction: 'reset',
|
||||||
schema: res.schema,
|
schema: res.schema,
|
||||||
configuration: res.configuration
|
configuration: res.configuration
|
||||||
});
|
});
|
||||||
this.setInitialConfig(res.configuration);
|
this.setInitialConfig(res.configuration);
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
this.authFetch(ATTACK_URL, {
|
this.authFetch(ATTACK_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
@ -298,23 +256,22 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
this.setState({attackConfig: res.configuration});
|
this.setState({attackConfig: res.configuration});
|
||||||
this.setInitialAttackConfig(res.configuration);
|
this.setInitialAttackConfig(res.configuration);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFileWindows)
|
||||||
|
this.removePBAfile(API_PBA_LINUX, this.setPbaFileLinux)
|
||||||
};
|
};
|
||||||
|
|
||||||
removePBAfiles() {
|
removePBAfile(apiEndpoint, setParamsFnc) {
|
||||||
// We need to clean files from widget, local state and configuration (to sync with bac end)
|
this.sendPbaRemoveRequest(apiEndpoint)
|
||||||
if (this.PBAwindowsPond !== null) {
|
setParamsFnc([], "")
|
||||||
this.PBAwindowsPond.removeFile();
|
}
|
||||||
}
|
|
||||||
if (this.PBAlinuxPond !== null) {
|
sendPbaRemoveRequest(apiEndpoint) {
|
||||||
this.PBAlinuxPond.removeFile();
|
|
||||||
}
|
|
||||||
let request_options = {
|
let request_options = {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {'Content-Type': 'text/plain'}
|
headers: {'Content-Type': 'text/plain'}
|
||||||
};
|
};
|
||||||
this.authFetch('/api/fileUpload/PBAlinux', request_options);
|
this.authFetch(apiEndpoint, request_options);
|
||||||
this.authFetch('/api/fileUpload/PBAwindows', request_options);
|
|
||||||
this.setState({PBAlinuxFile: [], PBAwinFile: []});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfigOnImport = (event) => {
|
setConfigOnImport = (event) => {
|
||||||
|
@ -366,82 +323,6 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
event.target.value = null;
|
event.target.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
PBAwindows = () => {
|
|
||||||
return (<FilePond
|
|
||||||
server={{
|
|
||||||
url: '/api/fileUpload/PBAwindows',
|
|
||||||
process: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
revert: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
restore: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
load: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
fetch: {headers: {'Authorization': this.jwtHeader}}
|
|
||||||
}}
|
|
||||||
files={this.getWinPBAfile()}
|
|
||||||
onupdatefiles={fileItems => {
|
|
||||||
this.setState({
|
|
||||||
PBAwinFile: fileItems.map(fileItem => fileItem.file)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
ref={ref => this.PBAwindowsPond = ref}
|
|
||||||
/>)
|
|
||||||
};
|
|
||||||
|
|
||||||
PBAlinux = () => {
|
|
||||||
return (<FilePond
|
|
||||||
server={{
|
|
||||||
url: '/api/fileUpload/PBAlinux',
|
|
||||||
process: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
revert: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
restore: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
load: {headers: {'Authorization': this.jwtHeader}},
|
|
||||||
fetch: {headers: {'Authorization': this.jwtHeader}}
|
|
||||||
}}
|
|
||||||
files={this.getLinuxPBAfile()}
|
|
||||||
onupdatefiles={fileItems => {
|
|
||||||
this.setState({
|
|
||||||
PBAlinuxFile: fileItems.map(fileItem => fileItem.file)
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
ref={ref => this.PBAlinuxPond = ref}
|
|
||||||
/>)
|
|
||||||
};
|
|
||||||
|
|
||||||
getWinPBAfile() {
|
|
||||||
if (this.state.PBAwinFile.length !== 0) {
|
|
||||||
return ConfigurePageComponent.getMockPBAfile(this.state.PBAwinFile[0])
|
|
||||||
} else if (this.state.configuration.monkey.behaviour.PBA_windows_filename) {
|
|
||||||
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_windows_filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLinuxPBAfile() {
|
|
||||||
if (this.state.PBAlinuxFile.length !== 0) {
|
|
||||||
return ConfigurePageComponent.getMockPBAfile(this.state.PBAlinuxFile[0])
|
|
||||||
} else if (this.state.configuration.monkey.behaviour.PBA_linux_filename) {
|
|
||||||
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_linux_filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFullPBAfile(filename) {
|
|
||||||
return [{
|
|
||||||
source: filename,
|
|
||||||
options: {
|
|
||||||
type: 'limbo'
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
static getMockPBAfile(mockFile) {
|
|
||||||
let pbaFile = [{
|
|
||||||
source: mockFile.name,
|
|
||||||
options: {
|
|
||||||
type: 'limbo'
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
pbaFile[0].options.file = mockFile;
|
|
||||||
return pbaFile
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMatrix = () => {
|
renderMatrix = () => {
|
||||||
return (<ConfigMatrixComponent configuration={this.state.attackConfig}
|
return (<ConfigMatrixComponent configuration={this.state.attackConfig}
|
||||||
submit={this.componentDidMount}
|
submit={this.componentDidMount}
|
||||||
|
@ -449,12 +330,15 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
change={this.attackTechniqueChange}/>)
|
change={this.attackTechniqueChange}/>)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
renderConfigContent = (displayedSchema) => {
|
renderConfigContent = (displayedSchema) => {
|
||||||
return (<div>
|
return (<div>
|
||||||
{this.renderBasicNetworkWarning()}
|
{this.renderBasicNetworkWarning()}
|
||||||
<Form schema={displayedSchema}
|
<Form schema={displayedSchema}
|
||||||
uiSchema={this.uiSchemas[this.state.selectedSection]}
|
uiSchema={UiSchema({
|
||||||
|
configuration: this.state,
|
||||||
|
setPbaFileWindows: this.setPbaFileWindows,
|
||||||
|
setPbaFileLinux: this.setPbaFileLinux,
|
||||||
|
})}
|
||||||
formData={this.state.configuration[this.state.selectedSection]}
|
formData={this.state.configuration[this.state.selectedSection]}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
noValidate={true}
|
noValidate={true}
|
||||||
|
@ -464,6 +348,27 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
</div>)
|
</div>)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setPbaFileWindows = (pbaFile, filename) => {
|
||||||
|
let pbaFileDeepCopy = JSON.parse(JSON.stringify(pbaFile))
|
||||||
|
let config = this.state.configuration
|
||||||
|
config.monkey.behaviour.PBA_windows_filename = filename
|
||||||
|
this.setState({
|
||||||
|
PBAwindowsFile: pbaFileDeepCopy,
|
||||||
|
configuration: config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setPbaFileLinux = (pbaFile, filename) => {
|
||||||
|
let pbaFileDeepCopy = JSON.parse(JSON.stringify(pbaFile))
|
||||||
|
let config = this.state.configuration
|
||||||
|
config.monkey.behaviour.PBA_linux_filename = filename
|
||||||
|
console.log(config);
|
||||||
|
this.setState({
|
||||||
|
PBAlinuxFile: pbaFileDeepCopy,
|
||||||
|
configuration: config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderBasicNetworkWarning = () => {
|
renderBasicNetworkWarning = () => {
|
||||||
if (this.state.selectedSection === 'basic_network') {
|
if (this.state.selectedSection === 'basic_network') {
|
||||||
return (<div className='alert alert-info'>
|
return (<div className='alert alert-info'>
|
||||||
|
@ -495,6 +400,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
||||||
displayedSchema['definitions'] = this.state.schema['definitions'];
|
displayedSchema['definitions'] = this.state.schema['definitions'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) {
|
if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) {
|
||||||
content = this.renderMatrix()
|
content = this.renderMatrix()
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
|
||||||
|
|
||||||
|
function ArrayFieldTemplate(props) {
|
||||||
|
return (
|
||||||
|
<div className={props.className}>
|
||||||
|
{props.items &&
|
||||||
|
props.items.map(element => (
|
||||||
|
<div key={element.key} className={element.className}>
|
||||||
|
<div>{element.children}</div>
|
||||||
|
{element.hasMoveDown && (
|
||||||
|
<button
|
||||||
|
onClick={element.onReorderClick(
|
||||||
|
element.index,
|
||||||
|
element.index + 1
|
||||||
|
)}>
|
||||||
|
Down
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{element.hasMoveUp && (
|
||||||
|
<button
|
||||||
|
onClick={element.onReorderClick(
|
||||||
|
element.index,
|
||||||
|
element.index - 1
|
||||||
|
)}>
|
||||||
|
Up
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button onClick={element.onDropIndexClick(element.index)}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{props.canAdd && (
|
||||||
|
<div className="row">
|
||||||
|
<p className="col-xs-3 col-xs-offset-9 array-item-add text-right">
|
||||||
|
<button onClick={props.onAddClick} type="button">
|
||||||
|
Custom +
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArrayFieldTemplate;
|
Loading…
Reference in New Issue