summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-19 15:47:23 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-19 16:03:11 +0200
commit5e5ab63b280eb446db4090733cd3ad2e97d02018 (patch)
tree352766be8a86c78f2aa233bb24db1a2711cc0f21 /opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack
parent54d07120191eb81de91a49cdebf619cfecce2666 (diff)
refactor(ui): Restructure components per page
This change updates the source structure of the OpenDC frontend to follow the page structure.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack')
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js44
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js59
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js46
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js73
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js56
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js58
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss12
8 files changed, 370 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
new file mode 100644
index 00000000..e944c2e8
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import PropTypes from 'prop-types'
+import React from 'react'
+import { useDispatch } from 'react-redux'
+import { Button } from '@patternfly/react-core'
+import { SaveIcon } from '@patternfly/react-icons'
+import { addPrefab } from '../../../../api/prefabs'
+
+function AddPrefab({ tileId }) {
+ const dispatch = useDispatch()
+ const onClick = () => dispatch(addPrefab('name', tileId))
+ return (
+ <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm">
+ Save this rack to a prefab
+ </Button>
+ )
+}
+
+AddPrefab.propTypes = {
+ tileId: PropTypes.string.isRequired,
+}
+
+export default AddPrefab
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js
new file mode 100644
index 00000000..80c6349a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import PropTypes from 'prop-types'
+import React, { useState } from 'react'
+import { useDispatch } from 'react-redux'
+import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon'
+import { Button } from '@patternfly/react-core'
+import ConfirmationModal from '../../../util/modals/ConfirmationModal'
+import { deleteRack } from '../../../../redux/actions/topology/rack'
+
+function DeleteRackContainer({ tileId }) {
+ const dispatch = useDispatch()
+ const [isVisible, setVisible] = useState(false)
+ const callback = (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteRack(tileId))
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}>
+ Delete this rack
+ </Button>
+ <ConfirmationModal
+ title="Delete this rack"
+ message="Are you sure you want to delete this rack?"
+ isOpen={isVisible}
+ callback={callback}
+ />
+ </>
+ )
+}
+
+DeleteRackContainer.propTypes = {
+ tileId: PropTypes.string.isRequired,
+}
+
+export default DeleteRackContainer
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js
new file mode 100644
index 00000000..921622d6
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js
@@ -0,0 +1,46 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+import Image from 'next/image'
+import { Flex, Label } from '@patternfly/react-core'
+import { Machine } from '../../../../shapes'
+
+const UnitIcon = ({ id, type }) => (
+ <Image
+ src={'/img/topology/' + id + '-icon.png'}
+ alt={'Machine contains ' + type + ' units'}
+ layout="intrinsic"
+ height={24}
+ width={24}
+ />
+)
+
+UnitIcon.propTypes = {
+ id: PropTypes.string,
+ type: PropTypes.string,
+}
+
+function MachineComponent({ machine, onClick }) {
+ const hasNoUnits =
+ machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0
+
+ return (
+ <Flex onClick={() => onClick()}>
+ {machine.cpus.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined}
+ {machine.gpus.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined}
+ {machine.memories.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined}
+ {machine.storages.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined}
+ {hasNoUnits ? (
+ <Label variant="outline" color="orange">
+ Machine with no units
+ </Label>
+ ) : undefined}
+ </Flex>
+ )
+}
+
+MachineComponent.propTypes = {
+ machine: Machine.isRequired,
+ onClick: PropTypes.func,
+}
+
+export default MachineComponent
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js
new file mode 100644
index 00000000..de7a2140
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js
@@ -0,0 +1,73 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+import MachineComponent from './MachineComponent'
+import {
+ Badge,
+ Button,
+ DataList,
+ DataListAction,
+ DataListCell,
+ DataListItem,
+ DataListItemCells,
+ DataListItemRow,
+} from '@patternfly/react-core'
+import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons'
+import { Machine } from '../../../../shapes'
+
+function MachineListComponent({ machines = [], onSelect, onAdd }) {
+ return (
+ <DataList aria-label="Rack Units">
+ {machines.map((machine, index) =>
+ machine ? (
+ <DataListItem key={index} onClick={() => onSelect(index + 1)}>
+ <DataListItemRow>
+ <DataListItemCells
+ dataListCells={[
+ <DataListCell isIcon key="icon">
+ <Badge isRead>{machines.length - index}U</Badge>
+ </DataListCell>,
+ <DataListCell key="primary content">
+ <MachineComponent onClick={() => onSelect(index + 1)} machine={machine} />
+ </DataListCell>,
+ ]}
+ />
+ <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto">
+ <Button isSmall variant="plain" className="pf-u-p-0">
+ <AngleRightIcon />
+ </Button>
+ </DataListAction>
+ </DataListItemRow>
+ </DataListItem>
+ ) : (
+ <DataListItem key={index}>
+ <DataListItemRow>
+ <DataListItemCells
+ dataListCells={[
+ <DataListCell isIcon key="icon">
+ <Badge isRead>{machines.length - index}U</Badge>
+ </DataListCell>,
+ <DataListCell key="add" className="text-secondary">
+ Empty Slot
+ </DataListCell>,
+ ]}
+ />
+ <DataListAction id="add" aria-label="Add Machine" aria-labelledby="add">
+ <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onAdd(index + 1)}>
+ <PlusIcon />
+ </Button>
+ </DataListAction>
+ </DataListItemRow>
+ </DataListItem>
+ )
+ )}
+ </DataList>
+ )
+}
+
+MachineListComponent.propTypes = {
+ machines: PropTypes.arrayOf(Machine),
+ onSelect: PropTypes.func.isRequired,
+ onAdd: PropTypes.func.isRequired,
+}
+
+export default MachineListComponent
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
new file mode 100644
index 00000000..6fbff949
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import PropTypes from 'prop-types'
+import React, { useMemo } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import MachineListComponent from './MachineListComponent'
+import { goFromRackToMachine } from '../../../../redux/actions/interaction-level'
+import { addMachine } from '../../../../redux/actions/topology/rack'
+
+function MachineListContainer({ tileId, ...props }) {
+ const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+ const machines = useSelector((state) => rack.machines.map((id) => state.objects.machine[id]))
+ const machinesNull = useMemo(() => {
+ const res = Array(rack.capacity).fill(null)
+ for (const machine of machines) {
+ res[machine.position - 1] = machine
+ }
+ return res
+ }, [rack, machines])
+ const dispatch = useDispatch()
+
+ return (
+ <MachineListComponent
+ {...props}
+ machines={machinesNull}
+ onAdd={(index) => dispatch(addMachine(rack._id, index))}
+ onSelect={(index) => dispatch(goFromRackToMachine(index))}
+ />
+ )
+}
+
+MachineListContainer.propTypes = {
+ tileId: PropTypes.string.isRequired,
+}
+
+export default MachineListContainer
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
new file mode 100644
index 00000000..09d73af7
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
@@ -0,0 +1,22 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import NameComponent from '../NameComponent'
+import { editRackName } from '../../../../redux/actions/topology/rack'
+
+const RackNameContainer = ({ tileId }) => {
+ const { name: rackName, _id } = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+ const dispatch = useDispatch()
+ const callback = (name) => {
+ if (name) {
+ dispatch(editRackName(_id, name))
+ }
+ }
+ return <NameComponent name={rackName} onEdit={callback} />
+}
+
+RackNameContainer.propTypes = {
+ tileId: PropTypes.string.isRequired,
+}
+
+export default RackNameContainer
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
new file mode 100644
index 00000000..3c9f152a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
@@ -0,0 +1,58 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss'
+import RackNameContainer from './RackNameContainer'
+import AddPrefab from './AddPrefab'
+import DeleteRackContainer from './DeleteRackContainer'
+import MachineListContainer from './MachineListContainer'
+import {
+ Skeleton,
+ TextContent,
+ TextList,
+ TextListItem,
+ TextListItemVariants,
+ TextListVariants,
+ Title,
+} from '@patternfly/react-core'
+import { useSelector } from 'react-redux'
+
+function RackSidebar({ tileId }) {
+ const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+
+ return (
+ <div className={sidebarContainer}>
+ <TextContent>
+ <Title headingLevel="h2">Details</Title>
+ <TextList component={TextListVariants.dl}>
+ <TextListItem
+ component={TextListItemVariants.dt}
+ className="pf-u-display-inline-flex pf-u-align-items-center"
+ >
+ Name
+ </TextListItem>
+ <TextListItem component={TextListItemVariants.dd}>
+ <RackNameContainer tileId={tileId} />
+ </TextListItem>
+ <TextListItem component={TextListItemVariants.dt}>Capacity</TextListItem>
+ <TextListItem component={TextListItemVariants.dd}>
+ {rack?.capacity ?? <Skeleton screenreaderText="Loading rack" />}
+ </TextListItem>
+ </TextList>
+ <Title headingLevel="h2">Actions</Title>
+ <AddPrefab tileId={tileId} />
+ <DeleteRackContainer tileId={tileId} />
+
+ <Title headingLevel="h2">Slots</Title>
+ </TextContent>
+ <div className={machineListContainer}>
+ <MachineListContainer tileId={tileId} />
+ </div>
+ </div>
+ )
+}
+
+RackSidebar.propTypes = {
+ tileId: PropTypes.string.isRequired,
+}
+
+export default RackSidebar
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss
new file mode 100644
index 00000000..6f258aec
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss
@@ -0,0 +1,12 @@
+.sidebarContainer {
+ display: flex;
+ height: 100%;
+ max-height: 100%;
+ flex-direction: column;
+}
+
+.machineListContainer {
+ flex: 1;
+ overflow-y: scroll;
+ margin-top: 10px;
+}