summaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
authorGeorgios Andreadis <info@gandreadis.com>2020-07-10 10:24:31 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2020-08-24 19:48:01 +0200
commit3b4e27320c479bd6ef48998f448ed070e8bd7511 (patch)
tree35ec6527e8d7a0b4093e18c8cb501c293a18b5eb /frontend/src/components
parentb30906bbe0d5f343b337a80de1b4b70ebf288331 (diff)
parent8aa174e70c01631ae4e00a6d208966fcd77cf972 (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.js4
-rw-r--r--frontend/src/components/app/sidebars/Sidebar.js12
-rw-r--r--frontend/src/components/app/sidebars/project/PortfolioListComponent.js59
-rw-r--r--frontend/src/components/app/sidebars/project/ProjectSidebarComponent.js7
-rw-r--r--frontend/src/components/app/sidebars/project/ScenarioListComponent.js54
-rw-r--r--frontend/src/components/app/sidebars/project/TopologyListComponent.js8
-rw-r--r--frontend/src/components/experiments/ExperimentListComponent.js59
-rw-r--r--frontend/src/components/experiments/ExperimentRowComponent.js36
-rw-r--r--frontend/src/components/experiments/NewExperimentButtonComponent.js17
-rw-r--r--frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js99
-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