From 7da6543fb9c58736c2fa8c000b25290be4f78de4 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 14 Sep 2022 14:12:41 +0200 Subject: fix(web/ui): Fix duplication of topology This change addresses an issue where a new topology did not correctly clone an existing topology. --- .../src/components/projects/NewTopologyModal.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/components/projects') diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js index be4256e3..780ec034 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js @@ -57,9 +57,10 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan setErrors({ name: true }) return false } else { - const candidate = topologies.find((topology) => topology.id === originTopology) || { projectId, rooms: [] } + const candidate = topologies.find((topology) => topology.id === originTopology) || { rooms: [] } const topology = produce(candidate, (draft) => { - delete draft.id + delete draft.project + draft.projectId = projectId draft.name = name }) onSubmitUpstream(topology) @@ -87,7 +88,12 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan - + setOriginTopology(+v)} + > {topologies.map((topology) => ( -- cgit v1.2.3 From 7199e2c15838d78fedd3c6127beddf1656dbeae2 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 15 Sep 2022 15:38:06 +0200 Subject: feat(web/ui): Redesign projects page This change updates the design of the projects page to use a gallery overview. --- .../src/components/projects/FilterPanel.js | 2 +- .../src/components/projects/NewProject.js | 39 ------ .../src/components/projects/NewProject.module.scss | 26 ---- .../src/components/projects/ProjectCollection.js | 137 +++++++++++++++++++++ .../src/components/projects/ProjectTable.js | 73 ----------- 5 files changed, 138 insertions(+), 139 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/components/projects/NewProject.js delete mode 100644 opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js delete mode 100644 opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js (limited to 'opendc-web/opendc-web-ui/src/components/projects') diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js index 285217e9..7c6d129c 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js @@ -6,7 +6,7 @@ import { filterPanel } from './FilterPanel.module.scss' export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' } const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => ( - + {Object.keys(FILTERS).map((filter) => ( { - const [isVisible, setVisible] = useState(false) - const { mutate: addProject } = useNewProject() - - const onSubmit = (name) => { - if (name) { - addProject({ name }) - } - setVisible(false) - } - - return ( - <> -
- -
- - - ) -} - -export default NewProject diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss b/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss deleted file mode 100644 index 5a0e74fc..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * 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. - */ - -.buttonContainer { - flex: 0 1 auto; - padding: 20px 0; -} diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js new file mode 100644 index 00000000..70f02812 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js @@ -0,0 +1,137 @@ +import { + Gallery, + Bullseye, + EmptyState, + EmptyStateIcon, + Card, + CardTitle, + CardActions, + DropdownItem, + CardHeader, + Dropdown, + KebabToggle, + CardBody, + CardHeaderMain, + TextVariants, + Text, + TextContent, + Tooltip, + Button, + Label, +} from '@patternfly/react-core' +import { PlusIcon, FolderIcon, TrashIcon } from '@patternfly/react-icons' +import PropTypes from 'prop-types' +import React, { useReducer, useMemo } from 'react' +import { Project, Status } from '../../shapes' +import { parseAndFormatDateTime } from '../../util/date-time' +import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP, AUTH_NAME_MAP } from '../../util/authorizations' +import NavItemLink from '../util/NavItemLink' +import TableEmptyState from '../util/TableEmptyState' + +function ProjectCard({ project, onDelete }) { + const [isKebabOpen, toggleKebab] = useReducer((t) => !t, false) + const { id, role, name, updatedAt } = project + const Icon = AUTH_ICON_MAP[role] + + return ( + + + + + + + + + + } + isOpen={isKebabOpen} + dropdownItems={[ + { + onDelete() + toggleKebab() + }} + position="right" + icon={} + > + Delete + , + ]} + /> + + + + {name} + + + + Last modified {parseAndFormatDateTime(updatedAt)} + + + + ) +} + +function ProjectCollection({ status, projects, onDelete, onCreate, isFiltering }) { + const sortedProjects = useMemo(() => { + const res = [...projects] + res.sort((a, b) => (new Date(a.updatedAt) < new Date(b.updatedAt) ? 1 : -1)) + return res + }, [projects]) + + if (sortedProjects.length === 0) { + return ( + } onClick={onCreate}> + Create Project + + } + /> + ) + } + + return ( + + {sortedProjects.map((project) => ( + onDelete(project)} /> + ))} + + + + + + + + + ) +} + +ProjectCollection.propTypes = { + status: Status.isRequired, + isFiltering: PropTypes.bool, + projects: PropTypes.arrayOf(Project).isRequired, + onDelete: PropTypes.func, + onCreate: PropTypes.func, +} + +export default ProjectCollection diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js deleted file mode 100644 index 6921578c..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js +++ /dev/null @@ -1,73 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Link from 'next/link' -import { Project, Status } from '../../shapes' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' -import { parseAndFormatDateTime } from '../../util/date-time' -import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' -import TableEmptyState from '../util/TableEmptyState' - -const ProjectTable = ({ status, projects, onDelete, isFiltering }) => { - const columns = ['Project name', 'Last edited', 'Access Rights'] - const rows = - projects.length > 0 - ? projects.map((project) => { - const Icon = AUTH_ICON_MAP[project.role] - return [ - { - title: {project.name}, - }, - parseAndFormatDateTime(project.updatedAt), - { - title: ( - <> - {AUTH_DESCRIPTION_MAP[project.role]} - - ), - }, - ] - }) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 3 }, - title: ( - - ), - }, - ], - }, - ] - - const actions = - projects.length > 0 - ? [ - { - title: 'Delete Project', - onClick: (_, rowId) => onDelete(projects[rowId]), - }, - ] - : [] - - return ( - - - -
- ) -} - -ProjectTable.propTypes = { - status: Status.isRequired, - isFiltering: PropTypes.bool, - projects: PropTypes.arrayOf(Project).isRequired, - onDelete: PropTypes.func, -} - -export default ProjectTable -- cgit v1.2.3 From 86bc9e74630374853d11bc1c8f7ba5ffafbaa868 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 20 Sep 2022 14:28:40 +0200 Subject: refactor(web/ui): Migrate to composable table This change updates the web interface to use the composable table API offered by PatternFly 4. This has replaced the legacy table API which will be removed in the next major version of PatternFly. --- .../src/components/projects/PortfolioTable.js | 105 ++++++++++++--------- .../src/components/projects/TopologyTable.js | 89 ++++++++--------- 2 files changed, 103 insertions(+), 91 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/components/projects') diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js index aa679843..0afeaeaf 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js @@ -20,64 +20,75 @@ * SOFTWARE. */ +import { Bullseye } from '@patternfly/react-core' import PropTypes from 'prop-types' import Link from 'next/link' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { TableComposable, Thead, Tbody, Tr, Th, Td, ActionsColumn } from '@patternfly/react-table' import React from 'react' import TableEmptyState from '../util/TableEmptyState' import { usePortfolios, useDeletePortfolio } from '../../data/project' -const PortfolioTable = ({ projectId }) => { +function PortfolioTable({ projectId }) { const { status, data: portfolios = [] } = usePortfolios(projectId) const { mutate: deletePortfolio } = useDeletePortfolio() - const columns = ['Name', 'Scenarios', 'Metrics', 'Repeats'] - const rows = - portfolios.length > 0 - ? portfolios.map((portfolio) => [ - { - title: ( - {portfolio.name} - ), - }, - portfolio.scenarios.length === 1 ? '1 scenario' : `${portfolio.scenarios.length} scenarios`, - portfolio.targets.metrics.length === 1 ? '1 metric' : `${portfolio.targets.metrics.length} metrics`, - portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`, - ]) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 4 }, - title: ( - - ), - }, - ], - }, - ] - - const actions = - portfolios.length > 0 - ? [ - { - title: 'Delete Portfolio', - onClick: (_, rowId) => deletePortfolio({ projectId, number: portfolios[rowId].number }), - }, - ] - : [] + const actions = (portfolio) => [ + { + title: 'Delete Portfolio', + onClick: () => deletePortfolio({ projectId, number: portfolio.number }), + }, + ] return ( - - - -
+ + + + Name + Scenarios + Metrics + Repeats + + + + {portfolios.map((portfolio) => ( + + + {portfolio.name} + + + {portfolio.scenarios.length === 1 + ? '1 scenario' + : `${portfolio.scenarios.length} scenarios`} + + + {portfolio.targets.metrics.length === 1 + ? '1 metric' + : `${portfolio.targets.metrics.length} metrics`} + + + {portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`} + + + + + + ))} + {portfolios.length === 0 && ( + + + + + + + + )} + + ) } diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js index ced5304a..62deace0 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js @@ -20,66 +20,67 @@ * SOFTWARE. */ +import { Bullseye } from '@patternfly/react-core' import PropTypes from 'prop-types' import Link from 'next/link' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { Tr, Th, Thead, Td, ActionsColumn, Tbody, TableComposable } from '@patternfly/react-table' import React from 'react' import TableEmptyState from '../util/TableEmptyState' import { parseAndFormatDateTime } from '../../util/date-time' import { useTopologies, useDeleteTopology } from '../../data/topology' -const TopologyTable = ({ projectId }) => { +function TopologyTable({ projectId }) { const { status, data: topologies = [] } = useTopologies(projectId) const { mutate: deleteTopology } = useDeleteTopology() - const columns = ['Name', 'Rooms', 'Last Edited'] - const rows = - topologies.length > 0 - ? topologies.map((topology) => [ - { - title: {topology.name}, - }, - topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`, - parseAndFormatDateTime(topology.updatedAt), - ]) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 3 }, - title: ( - - ), - }, - ], - }, - ] - - const actionResolver = (_, { rowIndex }) => [ + const actions = ({ number }) => [ { title: 'Delete Topology', - onClick: (_, rowId) => deleteTopology({ projectId, number: topologies[rowId].number }), - isDisabled: rowIndex === 0, + onClick: () => deleteTopology({ projectId, number }), + isDisabled: number === 0, }, ] return ( - 0 ? actionResolver : () => []} - > - - -
+ + + + Name + Rooms + Last Edited + + + + {topologies.map((topology) => ( + + + {topology.name} + + + {topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`} + + {parseAndFormatDateTime(topology.updatedAt)} + + + + + ))} + {topologies.length === 0 && ( + + + + + + + + )} + + ) } -- cgit v1.2.3