diff options
| author | Georgios Andreadis <info@gandreadis.com> | 2020-07-10 10:24:31 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2020-08-24 19:48:01 +0200 |
| commit | 3b4e27320c479bd6ef48998f448ed070e8bd7511 (patch) | |
| tree | 35ec6527e8d7a0b4093e18c8cb501c293a18b5eb /frontend/src/components | |
| parent | b30906bbe0d5f343b337a80de1b4b70ebf288331 (diff) | |
| parent | 8aa174e70c01631ae4e00a6d208966fcd77cf972 (diff) | |
Merge pull request #8 from atlarge-research/feature/portfolios-scenarios-frontend
Portfolios and scenarios on the frontend
Diffstat (limited to 'frontend/src/components')
| -rw-r--r-- | frontend/src/components/app/map/MapStageComponent.js | 4 | ||||
| -rw-r--r-- | frontend/src/components/app/sidebars/Sidebar.js | 12 | ||||
| -rw-r--r-- | frontend/src/components/app/sidebars/project/PortfolioListComponent.js | 59 | ||||
| -rw-r--r-- | frontend/src/components/app/sidebars/project/ProjectSidebarComponent.js | 7 | ||||
| -rw-r--r-- | frontend/src/components/app/sidebars/project/ScenarioListComponent.js | 54 | ||||
| -rw-r--r-- | frontend/src/components/app/sidebars/project/TopologyListComponent.js | 8 | ||||
| -rw-r--r-- | frontend/src/components/experiments/ExperimentListComponent.js | 59 | ||||
| -rw-r--r-- | frontend/src/components/experiments/ExperimentRowComponent.js | 36 | ||||
| -rw-r--r-- | frontend/src/components/experiments/NewExperimentButtonComponent.js | 17 | ||||
| -rw-r--r-- | frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js | 99 | ||||
| -rw-r--r-- | frontend/src/components/modals/custom-components/NewScenarioModalComponent.js (renamed from frontend/src/components/modals/custom-components/NewExperimentModalComponent.js) | 86 |
11 files changed, 296 insertions, 145 deletions
diff --git a/frontend/src/components/app/map/MapStageComponent.js b/frontend/src/components/app/map/MapStageComponent.js index 455604e4..f1c2b211 100644 --- a/frontend/src/components/app/map/MapStageComponent.js +++ b/frontend/src/components/app/map/MapStageComponent.js @@ -23,11 +23,9 @@ class MapStageComponent extends React.Component { this.updateScale = this.updateScale.bind(this) } - componentWillMount() { + componentDidMount() { this.updateDimensions() - } - componentDidMount() { window.addEventListener('resize', this.updateDimensions) window.addEventListener('wheel', this.updateScale) diff --git a/frontend/src/components/app/sidebars/Sidebar.js b/frontend/src/components/app/sidebars/Sidebar.js index 7ba8639a..a47a67c0 100644 --- a/frontend/src/components/app/sidebars/Sidebar.js +++ b/frontend/src/components/app/sidebars/Sidebar.js @@ -1,8 +1,18 @@ +import PropTypes from 'prop-types' import classNames from 'classnames' import React from 'react' import './Sidebar.css' class Sidebar extends React.Component { + static propTypes = { + isRight: PropTypes.bool.isRequired, + collapsible: PropTypes.bool, + } + + static defaultProps = { + collapsible: true + } + state = { collapsed: false, } @@ -41,7 +51,7 @@ class Sidebar extends React.Component { onWheel={e => e.stopPropagation()} > {this.props.children} - {collapseButton} + {this.props.collapsible && collapseButton} </div> ) } diff --git a/frontend/src/components/app/sidebars/project/PortfolioListComponent.js b/frontend/src/components/app/sidebars/project/PortfolioListComponent.js new file mode 100644 index 00000000..a31f11cf --- /dev/null +++ b/frontend/src/components/app/sidebars/project/PortfolioListComponent.js @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Shapes from '../../../../shapes' +import { Link } from 'react-router-dom' +import FontAwesome from 'react-fontawesome' +import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer' + +class PortfolioListComponent extends React.Component { + static propTypes = { + portfolios: PropTypes.arrayOf(Shapes.Portfolio), + currentProjectId: PropTypes.string.isRequired, + currentPortfolioId: PropTypes.string, + onNewPortfolio: PropTypes.func.isRequired, + onChoosePortfolio: PropTypes.func.isRequired, + onDeletePortfolio: PropTypes.func.isRequired, + } + + onDelete(id) { + this.props.onDeletePortfolio(id) + } + + render() { + return ( + <div className="pb-3"> + <h2> + Portfolios + <button className="btn btn-outline-primary float-right" onClick={this.props.onNewPortfolio.bind(this)}> + <FontAwesome name="plus"/> + </button> + </h2> + + {this.props.portfolios.map((portfolio, idx) => ( + <div key={portfolio._id}> + <div className="row mb-1"> + <div + className={'col-8 align-self-center ' + (portfolio._id === this.props.currentPortfolioId ? 'font-weight-bold' : '')}> + {portfolio.name} + </div> + <div className="col-4 text-right"> + <Link + className="btn btn-outline-primary mr-1 fa fa-play" + to={`/projects/${this.props.currentProjectId}/portfolios/${portfolio._id}`} + onClick={() => this.props.onChoosePortfolio(portfolio._id)} + /> + <span + className="btn btn-outline-danger fa fa-trash" + onClick={() => this.onDelete(portfolio._id)} + /> + </div> + </div> + <ScenarioListContainer portfolioId={portfolio._id}/> + </div> + ))} + </div> + ) + } +} + +export default PortfolioListComponent diff --git a/frontend/src/components/app/sidebars/project/ProjectSidebarComponent.js b/frontend/src/components/app/sidebars/project/ProjectSidebarComponent.js index d6e39ff6..b21b012b 100644 --- a/frontend/src/components/app/sidebars/project/ProjectSidebarComponent.js +++ b/frontend/src/components/app/sidebars/project/ProjectSidebarComponent.js @@ -1,11 +1,12 @@ import React from 'react' import Sidebar from '../Sidebar' import TopologyListContainer from '../../../../containers/app/sidebars/project/TopologyListContainer' +import PortfolioListContainer from '../../../../containers/app/sidebars/project/PortfolioListContainer' -const ProjectSidebarComponent = () => ( - <Sidebar isRight={false}> +const ProjectSidebarComponent = ({collapsible}) => ( + <Sidebar isRight={false} collapsible={collapsible}> <TopologyListContainer/> - <h2>Portfolios</h2> + <PortfolioListContainer/> </Sidebar> ) diff --git a/frontend/src/components/app/sidebars/project/ScenarioListComponent.js b/frontend/src/components/app/sidebars/project/ScenarioListComponent.js new file mode 100644 index 00000000..9d2e261e --- /dev/null +++ b/frontend/src/components/app/sidebars/project/ScenarioListComponent.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Shapes from '../../../../shapes' +import { Link } from 'react-router-dom' +import FontAwesome from 'react-fontawesome' + +class ScenarioListComponent extends React.Component { + static propTypes = { + scenarios: PropTypes.arrayOf(Shapes.Scenario), + portfolioId: PropTypes.string, + currentProjectId: PropTypes.string.isRequired, + currentScenarioId: PropTypes.string, + onNewScenario: PropTypes.func.isRequired, + onChooseScenario: PropTypes.func.isRequired, + onDeleteScenario: PropTypes.func.isRequired, + } + + onDelete(id) { + this.props.onDeleteScenario(id) + } + + render() { + return ( + <> + {this.props.scenarios.map((scenario, idx) => ( + <div key={scenario._id} className="row mb-1"> + <div className={'col-8 pl-5 align-self-center ' + (scenario._id === this.props.currentScenarioId ? 'font-weight-bold' : '')}> + {scenario.name} + </div> + <div className="col-4 text-right"> + <Link + className="btn btn-outline-primary mr-1 fa fa-play" + to={`/projects/${this.props.currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`} + onClick={() => this.props.onChooseScenario(scenario.portfolioId, scenario._id)} + /> + <span + className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} + onClick={() => idx !== 0 ? this.onDelete(scenario._id) : undefined} + /> + </div> + </div> + ))} + <div className="pl-4 mb-2"> + <div className="btn btn-outline-primary" onClick={() => this.props.onNewScenario(this.props.portfolioId)}> + <FontAwesome name="plus" className="mr-1"/> + New scenario + </div> + </div> + </> + ) + } +} + +export default ScenarioListComponent diff --git a/frontend/src/components/app/sidebars/project/TopologyListComponent.js b/frontend/src/components/app/sidebars/project/TopologyListComponent.js index 98615711..b8b41200 100644 --- a/frontend/src/components/app/sidebars/project/TopologyListComponent.js +++ b/frontend/src/components/app/sidebars/project/TopologyListComponent.js @@ -5,7 +5,6 @@ import FontAwesome from 'react-fontawesome' class TopologyListComponent extends React.Component { static propTypes = { - show: PropTypes.bool.isRequired, topologies: PropTypes.arrayOf(Shapes.Topology), currentTopologyId: PropTypes.string, onChooseTopology: PropTypes.func.isRequired, @@ -17,13 +16,6 @@ class TopologyListComponent extends React.Component { this.props.onChooseTopology(id) } - onDuplicate() { - this.props.onNewTopology( - this.textInput.value, - this.originTopology.value, - ) - } - onDelete(id) { this.props.onDeleteTopology(id) } diff --git a/frontend/src/components/experiments/ExperimentListComponent.js b/frontend/src/components/experiments/ExperimentListComponent.js deleted file mode 100644 index 3c53fc94..00000000 --- a/frontend/src/components/experiments/ExperimentListComponent.js +++ /dev/null @@ -1,59 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import ExperimentRowContainer from '../../containers/experiments/ExperimentRowContainer' - -const ExperimentListComponent = ({ experimentIds, loading }) => { - let alert - - if (loading) { - alert = ( - <div className="alert alert-success"> - <span className="fa fa-refresh fa-spin mr-2"/> - <strong>Loading Experiments...</strong> - </div> - ) - } else if (experimentIds.length === 0 && !loading) { - alert = ( - <div className="alert alert-info"> - <span className="fa fa-question-circle mr-2"/> - <strong>No experiments here yet...</strong> Add some with the button - below! - </div> - ) - } - - return ( - <div className="vertically-expanding-container"> - {alert ? ( - alert - ) : ( - <table className="table table-striped"> - <thead> - <tr> - <th>Name</th> - <th>Topology</th> - <th>Trace</th> - <th>Scheduler</th> - <th/> - </tr> - </thead> - <tbody> - {experimentIds.map(experimentId => ( - <ExperimentRowContainer - experimentId={experimentId} - key={experimentId} - /> - ))} - </tbody> - </table> - )} - </div> - ) -} - -ExperimentListComponent.propTypes = { - experimentIds: PropTypes.arrayOf(PropTypes.string).isRequired, - loading: PropTypes.bool, -} - -export default ExperimentListComponent diff --git a/frontend/src/components/experiments/ExperimentRowComponent.js b/frontend/src/components/experiments/ExperimentRowComponent.js deleted file mode 100644 index c6ae1ba4..00000000 --- a/frontend/src/components/experiments/ExperimentRowComponent.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Link } from 'react-router-dom' -import Shapes from '../../shapes/index' - -const ExperimentRowComponent = ({ experiment, projectId, onDelete }) => ( - <tr> - <td className="pt-3">{experiment.name}</td> - <td className="pt-3">{experiment.topology.name}</td> - <td className="pt-3">{experiment.trace.name}</td> - <td className="pt-3">{experiment.scheduler.name}</td> - <td className="text-right"> - <Link - to={'/projects/' + projectId + '/experiments/' + experiment._id} - className="btn btn-outline-primary btn-sm mr-2" - title="Open this experiment" - > - <span className="fa fa-play"/> - </Link> - <div - className="btn btn-outline-danger btn-sm" - title="Delete this experiment" - onClick={() => onDelete(experiment._id)} - > - <span className="fa fa-trash"/> - </div> - </td> - </tr> -) - -ExperimentRowComponent.propTypes = { - experiment: Shapes.Experiment.isRequired, - projectId: PropTypes.string.isRequired, -} - -export default ExperimentRowComponent diff --git a/frontend/src/components/experiments/NewExperimentButtonComponent.js b/frontend/src/components/experiments/NewExperimentButtonComponent.js deleted file mode 100644 index 2902825d..00000000 --- a/frontend/src/components/experiments/NewExperimentButtonComponent.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' - -const NewExperimentButtonComponent = ({ onClick }) => ( - <div className="bottom-btn-container"> - <div className="btn btn-primary float-right" onClick={onClick}> - <span className="fa fa-plus mr-2"/> - New Experiment - </div> - </div> -) - -NewExperimentButtonComponent.propTypes = { - onClick: PropTypes.func.isRequired, -} - -export default NewExperimentButtonComponent diff --git a/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js b/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js new file mode 100644 index 00000000..ace2d751 --- /dev/null +++ b/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js @@ -0,0 +1,99 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Modal from '../Modal' +import { AVAILABLE_METRICS } from '../../../util/available-metrics' + +class NewPortfolioModalComponent extends React.Component { + static propTypes = { + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + } + + constructor(props) { + super(props) + this.metricCheckboxes = {} + } + + componentDidMount() { + this.reset() + } + + reset() { + this.textInput.value = '' + AVAILABLE_METRICS.forEach(metric => { + this.metricCheckboxes[metric].checked = true + }) + this.repeatsInput.value = 1 + } + + onSubmit() { + this.props.callback( + this.textInput.value, + { + enabledMetrics: AVAILABLE_METRICS.filter(metric => this.metricCheckboxes[metric].checked), + repeatsPerScenario: parseInt(this.repeatsInput.value), + }, + ) + this.reset() + } + + onCancel() { + this.props.callback(undefined) + this.reset() + } + + render() { + return ( + <Modal + title="New Portfolio" + show={this.props.show} + onSubmit={this.onSubmit.bind(this)} + onCancel={this.onCancel.bind(this)} + > + <form + onSubmit={e => { + e.preventDefault() + this.onSubmit() + }} + > + <div className="form-group"> + <label className="form-control-label">Name</label> + <input + type="text" + className="form-control" + required + ref={textInput => (this.textInput = textInput)} + /> + </div> + <h4>Targets</h4> + <h5>Metrics</h5> + <div className="form-group"> + {AVAILABLE_METRICS.map(metric => ( + <div className="form-check" key={metric}> + <label className="form-check-label"> + <input + type="checkbox" + className="form-check-input" + ref={checkbox => (this.metricCheckboxes[metric] = checkbox)} + /> + <code>{metric}</code> + </label> + </div> + ))} + </div> + <div className="form-group"> + <label className="form-control-label">Repeats per scenario</label> + <input + type="number" + className="form-control" + required + ref={repeatsInput => (this.repeatsInput = repeatsInput)} + /> + </div> + </form> + </Modal> + ) + } +} + +export default NewPortfolioModalComponent diff --git a/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js b/frontend/src/components/modals/custom-components/NewScenarioModalComponent.js index ce685837..4c2df2f6 100644 --- a/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js +++ b/frontend/src/components/modals/custom-components/NewScenarioModalComponent.js @@ -3,28 +3,46 @@ import React from 'react' import Shapes from '../../../shapes' import Modal from '../Modal' -class NewExperimentModalComponent extends React.Component { +class NewScenarioModalComponent extends React.Component { static propTypes = { show: PropTypes.bool.isRequired, + currentPortfolioId: PropTypes.string.isRequired, + traces: PropTypes.arrayOf(Shapes.Trace), topologies: PropTypes.arrayOf(Shapes.Topology), schedulers: PropTypes.arrayOf(Shapes.Scheduler), - traces: PropTypes.arrayOf(Shapes.Trace), callback: PropTypes.func.isRequired, } + componentDidMount() { + this.reset() + } + reset() { this.textInput.value = '' - this.topologySelect.selectedIndex = 0 this.traceSelect.selectedIndex = 0 + this.traceLoadInput.value = 1.0 + this.topologySelect.selectedIndex = 0 + this.failuresCheckbox.checked = false + this.performanceInterferenceCheckbox.checked = false this.schedulerSelect.selectedIndex = 0 } onSubmit() { this.props.callback( this.textInput.value, - this.topologySelect.value, - this.traceSelect.value, - this.schedulerSelect.value, + this.props.currentPortfolioId, + { + traceId: this.traceSelect.value, + loadSamplingFraction: parseFloat(this.traceLoadInput.value), + }, + { + topologyId: this.topologySelect.value + }, + { + failuresEnabled: this.failuresCheckbox.checked, + performanceInterferenceEnabled: this.performanceInterferenceCheckbox.checked, + schedulerName: this.schedulerSelect.value, + }, ) this.reset() } @@ -37,7 +55,7 @@ class NewExperimentModalComponent extends React.Component { render() { return ( <Modal - title="New Experiment" + title="New Scenario" show={this.props.show} onSubmit={this.onSubmit.bind(this)} onCancel={this.onCancel.bind(this)} @@ -57,32 +75,64 @@ class NewExperimentModalComponent extends React.Component { ref={textInput => (this.textInput = textInput)} /> </div> + <h4>Trace</h4> <div className="form-group"> - <label className="form-control-label">Topology</label> + <label className="form-control-label">Trace</label> <select className="form-control" - ref={topologySelect => (this.topologySelect = topologySelect)} + ref={traceSelect => (this.traceSelect = traceSelect)} > - {this.props.topologies.map(topology => ( - <option value={topology._id} key={topology._id}> - {topology.name} + {this.props.traces.map(trace => ( + <option value={trace._id} key={trace._id}> + {trace.name} </option> ))} </select> </div> <div className="form-group"> - <label className="form-control-label">Trace</label> + <label className="form-control-label">Load sampling fraction</label> + <input + type="number" + className="form-control" + required + ref={traceLoadInput => (this.traceLoadInput = traceLoadInput)} + /> + </div> + <h4>Topology</h4> + <div className="form-group"> + <label className="form-control-label">Topology</label> <select className="form-control" - ref={traceSelect => (this.traceSelect = traceSelect)} + ref={topologySelect => (this.topologySelect = topologySelect)} > - {this.props.traces.map(trace => ( - <option value={trace._id} key={trace._id}> - {trace.name} + {this.props.topologies.map(topology => ( + <option value={topology._id} key={topology._id}> + {topology.name} </option> ))} </select> </div> + <h4>Operational Phenomena</h4> + <div className="form-check"> + <label className="form-check-label"> + <input + type="checkbox" + className="form-check-input" + ref={failuresCheckbox => (this.failuresCheckbox = failuresCheckbox)} + /> + <span className="ml-2">Enable failures</span> + </label> + </div> + <div className="form-check"> + <label className="form-check-label"> + <input + type="checkbox" + className="form-check-input" + ref={performanceInterferenceCheckbox => (this.performanceInterferenceCheckbox = performanceInterferenceCheckbox)} + /> + <span className="ml-2">Enable performance interference</span> + </label> + </div> <div className="form-group"> <label className="form-control-label">Scheduler</label> <select @@ -102,4 +152,4 @@ class NewExperimentModalComponent extends React.Component { } } -export default NewExperimentModalComponent +export default NewScenarioModalComponent |
