From 803e13b32cf0ff8b496649fb0a4d6e32400e98a4 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 14 Jul 2021 22:23:40 +0200 Subject: feat(ui): Migrate to PatternFly 4 design framework This change is a rewrite of the existing OpenDC frontend in order to migrate to the PatternFly 4 design framework. PatternFly is used by Red Hat for various computing related services such as OpenShift, Red Hat Virtualization and Cockpit. Since their design requirements are very similar to those of OpenDC (modeling computing services), migrating to PatternFly 4 allows us to re-use design choices from these services. See https://www.patternfly.org/v4/ for more information about PatternFly. --- .../modals/custom-components/NewPortfolioModal.js | 139 ++++++++++++++++++ .../NewPortfolioModalComponent.js | 78 ---------- .../modals/custom-components/NewScenarioModal.js | 159 +++++++++++++++++++++ .../custom-components/NewScenarioModalComponent.js | 144 ------------------- .../modals/custom-components/NewTopologyModal.js | 81 +++++++++++ .../custom-components/NewTopologyModalComponent.js | 71 --------- 6 files changed, 379 insertions(+), 293 deletions(-) create mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js (limited to 'opendc-web/opendc-web-ui/src/components/modals/custom-components') diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js new file mode 100644 index 00000000..afe07597 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js @@ -0,0 +1,139 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../Modal' +import { + Form, + FormGroup, + FormSection, + NumberInput, + Select, + SelectGroup, + SelectOption, + SelectVariant, + TextInput, +} from '@patternfly/react-core' +import { METRIC_GROUPS, METRIC_NAMES } from '../../../util/available-metrics' + +const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { + const nameInput = useRef(null) + const [repeats, setRepeats] = useState(1) + const [isSelectOpen, setSelectOpen] = useState(false) + const [selectedMetrics, setSelectedMetrics] = useState([]) + + const [isSubmitted, setSubmitted] = useState(false) + const [errors, setErrors] = useState({}) + + const clearState = () => { + setSubmitted(false) + setErrors({}) + nameInput.current.value = '' + setRepeats(1) + setSelectOpen(false) + setSelectedMetrics([]) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else { + onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) + } + + clearState() + return false + } + const onCancel = () => { + onUpstreamCancel() + clearState() + } + + const onSelect = (event, selection) => { + if (selectedMetrics.includes(selection)) { + setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) + } else { + setSelectedMetrics((metrics) => [...metrics, selection]) + } + } + + return ( + +
+ + + + + + + + + + + setRepeats(Number(e.target.value))} + onPlus={() => setRepeats((r) => r + 1)} + onMinus={() => setRepeats((r) => r - 1)} + min={1} + /> + + +
+
+ ) +} + +NewPortfolioModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js deleted file mode 100644 index 3c6b8724..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js +++ /dev/null @@ -1,78 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import Modal from '../Modal' -import { AVAILABLE_METRICS, METRIC_NAMES } from '../../../util/available-metrics' - -const NewPortfolioModalComponent = ({ show, callback }) => { - const form = useRef(null) - const textInput = useRef(null) - const repeatsInput = useRef(null) - const metricCheckboxes = useRef({}) - - const onSubmit = () => { - if (form.current.reportValidity()) { - callback(textInput.current.value, { - enabledMetrics: AVAILABLE_METRICS.filter((metric) => metricCheckboxes.current[metric].checked), - repeatsPerScenario: parseInt(repeatsInput.current.value), - }) - - return true - } else { - return false - } - } - const onCancel = () => callback(undefined) - - return ( - -
{ - e.preventDefault() - this.onSubmit() - }} - innerRef={form} - > - - - - -

Targets

-
Metrics
- - {AVAILABLE_METRICS.map((metric) => ( - - - - ))} - - - - - -
-
- ) -} - -NewPortfolioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, -} - -export default NewPortfolioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js new file mode 100644 index 00000000..94d0d424 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js @@ -0,0 +1,159 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Portfolio } from '../../../shapes' +import Modal from '../Modal' +import { + Checkbox, + Form, + FormGroup, + FormSection, + FormSelect, + FormSelectOption, + NumberInput, + TextInput, +} from '@patternfly/react-core' +import { useSchedulers, useTraces } from '../../../data/experiments' +import { useProjectTopologies } from '../../../data/topology' +import { usePortfolio } from '../../../data/project' + +const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) + const { data: traces = [] } = useTraces() + const { data: schedulers = [] } = useSchedulers() + + const [isSubmitted, setSubmitted] = useState(false) + const [traceLoad, setTraceLoad] = useState(100) + const [trace, setTrace] = useState(undefined) + const [topology, setTopology] = useState(undefined) + const [scheduler, setScheduler] = useState(undefined) + const [failuresEnabled, setFailuresEnabled] = useState(false) + const [opPhenEnabled, setOpPhenEnabled] = useState(false) + const nameInput = useRef(null) + + const resetState = () => { + setSubmitted(false) + setTraceLoad(100) + setTrace(undefined) + setTopology(undefined) + setScheduler(undefined) + setFailuresEnabled(false) + setOpPhenEnabled(false) + nameInput.current.value = '' + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + onSubmitUpstream( + name, + portfolio._id, + { + traceId: trace || traces[0]._id, + loadSamplingFraction: traceLoad / 100, + }, + { + topologyId: topology || topologies[0]._id, + }, + { + failuresEnabled, + performanceInterferenceEnabled: opPhenEnabled, + schedulerName: scheduler || schedulers[0].name, + } + ) + + resetState() + return true + } + const onCancel = () => { + onCancelUpstream() + resetState() + } + + return ( + +
+ + + + + + + {traces.map((trace) => ( + + ))} + + + + setTraceLoad((load) => load - 1)} + onPlus={() => setTraceLoad((load) => load + 1)} + onChange={(e) => setTraceLoad(Number(e.target.value))} + unit="%" + /> + + + + + + {topologies.map((topology) => ( + + ))} + + + + + + {schedulers.map((scheduler) => ( + + ))} + + + + + setFailuresEnabled((e) => !e)} + /> + setOpPhenEnabled((e) => !e)} + /> + +
+
+ ) +} + +NewScenarioModal.propTypes = { + portfolioId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js deleted file mode 100644 index 782812ac..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import { Scheduler, Topology, Trace } from '../../../shapes' -import Modal from '../Modal' - -const NewScenarioModalComponent = ({ - show, - callback, - currentPortfolioId, - currentPortfolioScenarioIds, - traces, - topologies, - schedulers, -}) => { - const form = useRef(null) - const textInput = useRef(null) - const traceSelect = useRef(null) - const traceLoadInput = useRef(null) - const topologySelect = useRef(null) - const failuresCheckbox = useRef(null) - const performanceInterferenceCheckbox = useRef(null) - const schedulerSelect = useRef(null) - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } - callback( - textInput.current.value, - currentPortfolioId, - { - traceId: traceSelect.current.value, - loadSamplingFraction: parseFloat(traceLoadInput.current.value), - }, - { - topologyId: topologySelect.current.value, - }, - { - failuresEnabled: failuresCheckbox.current.checked, - performanceInterferenceEnabled: performanceInterferenceCheckbox.current.checked, - schedulerName: schedulerSelect.current.value, - } - ) - return true - } - const onCancel = () => { - callback(undefined) - } - - return ( - -
{ - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - - - - -

Trace

- - - - {traces.map((trace) => ( - - ))} - - - - - - -

Topology

-
- - - {topologies.map((topology) => ( - - ))} - -
-

Operational Phenomena

- - - - - - - - - - {schedulers.map((scheduler) => ( - - ))} - - -
-
- ) -} - -NewScenarioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - currentPortfolioId: PropTypes.string.isRequired, - currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), - traces: PropTypes.arrayOf(Trace), - topologies: PropTypes.arrayOf(Topology), - schedulers: PropTypes.arrayOf(Scheduler), - callback: PropTypes.func.isRequired, -} - -export default NewScenarioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js new file mode 100644 index 00000000..49952aec --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js @@ -0,0 +1,81 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../Modal' +import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' +import { useProjectTopologies } from '../../../data/topology' + +const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const nameInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [originTopology, setOriginTopology] = useState(-1) + const [errors, setErrors] = useState({}) + + const { data: topologies = [] } = useProjectTopologies(projectId) + + const clearState = () => { + nameInput.current.value = '' + setSubmitted(false) + setOriginTopology(-1) + setErrors({}) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else if (originTopology === -1) { + onSubmitUpstream(name) + } else { + onSubmitUpstream(name, originTopology) + } + + clearState() + return true + } + + const onCancel = () => { + onCancelUpstream() + clearState() + } + + return ( + +
+ + + + + + + {topologies.map((topology) => ( + + ))} + + +
+
+ ) +} + +NewTopologyModal.propTypes = { + projectId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js deleted file mode 100644 index f06fe797..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import React, { useRef } from 'react' -import { Topology } from '../../../shapes' -import Modal from '../Modal' - -const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { - const form = useRef(null) - const textInput = useRef(null) - const originTopology = useRef(null) - - const onCreate = () => { - onCreateTopology(textInput.current.value) - } - - const onDuplicate = () => { - onDuplicateTopology(textInput.current.value, originTopology.current.value) - } - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } else if (originTopology.current.selectedIndex === 0) { - onCreate() - } else { - onDuplicate() - } - - return true - } - - return ( - -
{ - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - - - - - - - - - {topologies.map((topology) => ( - - ))} - - -
-
- ) -} - -NewTopologyModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - topologies: PropTypes.arrayOf(Topology), - onCreateTopology: PropTypes.func.isRequired, - onDuplicateTopology: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewTopologyModalComponent -- cgit v1.2.3