From fcef818aac13abc863aa1fb51ed1a67a2c413cdb Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Thu, 2 Feb 2023 16:22:01 +0100 Subject: [PATCH 1/6] Add stepper to role modals --- .../components/account/roles/RoleCreate.tsx | 13 +- .../src/components/account/roles/RoleEdit.tsx | 13 +- .../src/components/account/roles/RoleForm.tsx | 177 ++++++++++++------ .../roles/RoleFormGeneralAttributes.tsx | 14 +- .../account/roles/RolePermissionToggle.tsx | 39 ++-- 5 files changed, 152 insertions(+), 104 deletions(-) diff --git a/assets/src/components/account/roles/RoleCreate.tsx b/assets/src/components/account/roles/RoleCreate.tsx index 6476419fc1..022bf01be1 100644 --- a/assets/src/components/account/roles/RoleCreate.tsx +++ b/assets/src/components/account/roles/RoleCreate.tsx @@ -7,8 +7,6 @@ import isEqual from 'lodash/isEqual' import { appendConnection, updateCache } from '../../../utils/graphql' -import { Actions } from '../../utils/Actions' - import { sanitize } from './misc' import { CREATE_ROLE, ROLES_Q } from './queries' @@ -54,24 +52,19 @@ export default function RoleCreate({ q }: any) { Create role resetAndClose()} marginVertical={16} size="large" - actions={( - resetAndClose()} - submit={() => mutation()} - loading={loading} - /> - )} > resetAndClose()} + submit={() => mutation()} + loading={loading} error={error} /> diff --git a/assets/src/components/account/roles/RoleEdit.tsx b/assets/src/components/account/roles/RoleEdit.tsx index a0dbc641bc..39275a4659 100644 --- a/assets/src/components/account/roles/RoleEdit.tsx +++ b/assets/src/components/account/roles/RoleEdit.tsx @@ -6,8 +6,6 @@ import isEqual from 'lodash/isEqual' import pick from 'lodash/pick' -import { Actions } from '../../utils/Actions' - import { sanitize } from './misc' import { UPDATE_ROLE } from './queries' @@ -32,20 +30,15 @@ export default function RoleEdit({ role, open, setOpen }: any) { open={open} size="large" onClose={() => setOpen(false)} - actions={( - setOpen(false)} - submit={() => mutation()} - loading={loading} - action="Update" - /> - )} > setOpen(false)} + submit={() => mutation()} + loading={loading} error={error} /> diff --git a/assets/src/components/account/roles/RoleForm.tsx b/assets/src/components/account/roles/RoleForm.tsx index e26a77c634..bceb03d2de 100644 --- a/assets/src/components/account/roles/RoleForm.tsx +++ b/assets/src/components/account/roles/RoleForm.tsx @@ -1,96 +1,155 @@ import { Box } from 'grommet' -import { Span } from 'honorable' -import { Tab, TabList, TabPanel } from '@pluralsh/design-system' -import { useRef, useState } from 'react' +import { Div, Flex, P } from 'honorable' +import { + BriefcaseIcon, + Button, + PeopleIcon, + Stepper, + ValidatedInput, +} from '@pluralsh/design-system' +import { useState } from 'react' +import { StepperSteps } from '@pluralsh/design-system/dist/components/Stepper' import { GqlError } from '../../utils/Alert' import RoleFormGeneralAttributes from './RoleFormGeneralAttributes' - import RolePermissionToggle from './RolePermissionToggle' -const TABS = { - General: { label: 'General' }, - Permissions: { label: 'Permissions' }, +const stepBase = { + circleSize: 32, + iconSize: 16, + vertical: true, } +const steps: StepperSteps = [ + { + key: 'create-repo', + stepTitle:
Role info
, + IconComponent: BriefcaseIcon, + ...stepBase, + }, + { + key: 'choose-cloud', + stepTitle:
Bindings
, + IconComponent: PeopleIcon, + ...stepBase, + }, +] + const PermissionTypes = { - READ: 'can view components', - CONFIGURE: 'can edit helm/terraform configuration', - DEPLOY: 'can create/approve deployments', - OPERATE: 'can delete pods, export logs, and other operational tasks', + READ: 'Can view components', + CONFIGURE: 'Can edit helm/terraform configuration', + DEPLOY: 'Can create/approve deployments', + OPERATE: 'Can delete pods, export logs, and other operational tasks', } export default function RoleForm({ + cancel, + submit, + loading, error, attributes, setAttributes, bindings, setBindings, - ...box }): any { - const [view, setView] = useState('General') + const [step, setStep] = useState<0|1>(0) const permissions = Object.entries(PermissionTypes) const len = permissions.length - const tabStateRef = useRef(null) return ( + + + {error && ( )} - setView(key as string), - }} - > - {Object.entries(TABS).map(([key, { label }]) => ( - {label} - ))} - - - {view === 'General' && ( - - )} - {view === 'Permissions' && ( - - - Permissions - - Grant permissions to all users and groups bound to this role - - - - {permissions.map(([perm, description], i) => ( - - ))} - + + {/* ROLE INFO */} + {step === 0 && ( + + + setAttributes({ ...attributes, name: value })} + /> + setAttributes({ ...attributes, description: value })} + /> +

+ Permissions +

+ {permissions.map(([perm, description], i) => ( + + ))}
- )} -
+
+ )} + + {/* BINDINGS */} + {step === 1 && ( + + )} + + + + + + + + ) } diff --git a/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx b/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx index 6dc2fc034c..de5d4316b9 100644 --- a/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx +++ b/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx @@ -17,18 +17,8 @@ export default function RoleFormGeneralAttributes({ gap="small" > setAttributes({ ...attributes, name: value })} - /> - setAttributes({ ...attributes, description: value })} - /> - { setRepositories(value) diff --git a/assets/src/components/account/roles/RolePermissionToggle.tsx b/assets/src/components/account/roles/RolePermissionToggle.tsx index 1d516f4d2b..c1d9ae4073 100644 --- a/assets/src/components/account/roles/RolePermissionToggle.tsx +++ b/assets/src/components/account/roles/RolePermissionToggle.tsx @@ -1,6 +1,9 @@ -import { ListItem } from 'components/utils/List' -import { Box } from 'grommet' -import { Span, Switch } from 'honorable' +import { + Div, + Flex, + P, + Switch, +} from 'honorable' import { useCallback } from 'react' export default function RolePermissionToggle({ @@ -8,7 +11,6 @@ export default function RolePermissionToggle({ description, attributes, setAttributes, - first, last, }: any) { const toggle = useCallback(enable => { @@ -28,19 +30,30 @@ export default function RolePermissionToggle({ [permission, attributes, setAttributes]) return ( - - - {permission.toLowerCase()} - {description} - +
+

+ {permission.toLowerCase()} +

+

+ {description} +

+
perm === permission)} onChange={({ target: { checked } }) => toggle(checked)} /> -
+ ) } From 2a5c3b8798786c3904aa38ebdb3303b690d71bbf Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Fri, 3 Feb 2023 09:58:23 +0100 Subject: [PATCH 2/6] Finish role modal redesign --- .../src/components/account/roles/RoleForm.tsx | 139 ++++++++++-------- ...ralAttributes.tsx => RoleFormBindings.tsx} | 2 +- 2 files changed, 78 insertions(+), 63 deletions(-) rename assets/src/components/account/roles/{RoleFormGeneralAttributes.tsx => RoleFormBindings.tsx} (96%) diff --git a/assets/src/components/account/roles/RoleForm.tsx b/assets/src/components/account/roles/RoleForm.tsx index bceb03d2de..bed75e5943 100644 --- a/assets/src/components/account/roles/RoleForm.tsx +++ b/assets/src/components/account/roles/RoleForm.tsx @@ -1,4 +1,3 @@ -import { Box } from 'grommet' import { Div, Flex, P } from 'honorable' import { BriefcaseIcon, @@ -12,7 +11,7 @@ import { StepperSteps } from '@pluralsh/design-system/dist/components/Stepper' import { GqlError } from '../../utils/Alert' -import RoleFormGeneralAttributes from './RoleFormGeneralAttributes' +import RoleFormBindings from './RoleFormBindings' import RolePermissionToggle from './RolePermissionToggle' const stepBase = { @@ -58,47 +57,45 @@ export default function RoleForm({ const len = permissions.length return ( - - + - {error && ( - - )} {/* ROLE INFO */} {step === 0 && ( - - - setAttributes({ ...attributes, name: value })} - /> - setAttributes({ ...attributes, description: value })} - /> -

- Permissions -

+ + setAttributes({ ...attributes, name: value })} + /> + setAttributes({ ...attributes, description: value })} + /> +

+ Permissions +

+
{permissions.map(([perm, description], i) => ( ))} - - +
+
)} {/* BINDINGS */} {step === 1 && ( - )} - - - + {error && ( + + )} - - + + {step === 0 && ( + <> + + + + )} + + {step === 1 && ( + <> + + + + )} -
+
) } diff --git a/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx b/assets/src/components/account/roles/RoleFormBindings.tsx similarity index 96% rename from assets/src/components/account/roles/RoleFormGeneralAttributes.tsx rename to assets/src/components/account/roles/RoleFormBindings.tsx index de5d4316b9..02af1f69d2 100644 --- a/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx +++ b/assets/src/components/account/roles/RoleFormBindings.tsx @@ -3,7 +3,7 @@ import { ValidatedInput } from '@pluralsh/design-system' import { useState } from 'react' import { BindingInput } from 'components/utils/BindingInput' -export default function RoleFormGeneralAttributes({ +export default function RoleFormBindings({ attributes, setAttributes, bindings, From 0c2816f43063454ed7ff1b6a88b63262b274925b Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Fri, 3 Feb 2023 10:51:11 +0100 Subject: [PATCH 3/6] Update delete message --- assets/src/components/account/roles/Role.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/components/account/roles/Role.tsx b/assets/src/components/account/roles/Role.tsx index 79b058d873..2239840bcb 100644 --- a/assets/src/components/account/roles/Role.tsx +++ b/assets/src/components/account/roles/Role.tsx @@ -74,7 +74,7 @@ export default function Role({ role, q }: any) { setConfirm(false)} submit={() => mutation()} loading={loading} From 446cf9f676d883c034664bb8cb48f034d31d741f Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Fri, 3 Feb 2023 11:06:13 +0100 Subject: [PATCH 4/6] Redesign group edit dialog --- .../components/account/groups/GroupEdit.tsx | 218 +++++++++++------- .../components/account/roles/RoleCreate.tsx | 1 + .../src/components/account/roles/RoleEdit.tsx | 1 + .../src/components/account/roles/RoleForm.tsx | 7 +- 4 files changed, 137 insertions(+), 90 deletions(-) diff --git a/assets/src/components/account/groups/GroupEdit.tsx b/assets/src/components/account/groups/GroupEdit.tsx index 9cf3697e92..f2c0acb286 100644 --- a/assets/src/components/account/groups/GroupEdit.tsx +++ b/assets/src/components/account/groups/GroupEdit.tsx @@ -1,31 +1,48 @@ import { useApolloClient, useMutation } from '@apollo/client' import { + Button, ComboBox, FormField, Modal, - Tab, - TabList, - TabPanel, + PeopleIcon, + PersonPlusIcon, + Stepper, ValidatedInput, } from '@pluralsh/design-system' -import { useEffect, useRef, useState } from 'react' -import { Flex } from 'honorable' +import { useEffect, useState } from 'react' +import { Div, Flex } from 'honorable' import { fetchUsers } from 'components/utils/BindingInput' -import { GqlError } from '../../utils/Alert' +import { StepperSteps } from '@pluralsh/design-system/dist/components/Stepper' -import { Actions } from '../../utils/Actions' +import { GqlError } from '../../utils/Alert' import { CREATE_GROUP_MEMBERS, GROUP_MEMBERS, UPDATE_GROUP } from './queries' import GroupMembers from './GroupMembers' -const TABS = { - Attributes: { label: 'Attributes' }, - Users: { label: 'Users' }, +const stepBase = { + circleSize: 32, + iconSize: 16, + vertical: true, } +const steps: StepperSteps = [ + { + key: 'info', + stepTitle:
Group info
, + IconComponent: PeopleIcon, + ...stepBase, + }, + { + key: 'bindings', + stepTitle:
User bindings
, + IconComponent: PersonPlusIcon, + ...stepBase, + }, +] + export default function GroupEdit({ group, edit, setEdit }: any) { const client = useApolloClient() const [value, setValue] = useState('') @@ -40,8 +57,7 @@ export default function GroupEdit({ group, edit, setEdit }: any) { refetchQueries: [{ query: GROUP_MEMBERS, variables: { id: group.id } }], }) const [suggestions, setSuggestions] = useState([]) - const tabStateRef = useRef(null) - const [view, setView] = useState('Attributes') + const [step, setStep] = useState<0|1>(0) // Run only on first render. Make sure there will be data in Combo Box to start with. // eslint-disable-next-line react-hooks/exhaustive-deps @@ -54,98 +70,126 @@ export default function GroupEdit({ group, edit, setEdit }: any) { return ( setEdit(false)} - actions={( - setEdit(false)} - submit={() => mutation()} - loading={loading} - action="Update" - /> - )} > + + + + + {/* Group info */} + {step === 0 && ( + + setName(value)} + /> + setDescription(value)} + /> + + )} + + {/* Bindings */} + {step === 1 && ( + + + { + setValue('') + // @ts-expect-error + addMut({ variables: { userId: key } }) + }} + onInputChange={value => { + setValue(value) + fetchUsers(client, value, setSuggestions) + }} + > + {suggestions.map(({ label }) => label)} + + + + + )} + {error && ( )} - setView(key as string), - }} + + - {Object.entries(TABS).map(([key, { label }]) => ( - {label} - ))} - - - {view === 'Attributes' && ( - - setName(value)} - /> - setDescription(value)} - /> - + {step === 0 && ( + <> + + + )} - {view === 'Users' && ( - - + + + )} - + ) diff --git a/assets/src/components/account/roles/RoleCreate.tsx b/assets/src/components/account/roles/RoleCreate.tsx index 022bf01be1..97deefb7cf 100644 --- a/assets/src/components/account/roles/RoleCreate.tsx +++ b/assets/src/components/account/roles/RoleCreate.tsx @@ -62,6 +62,7 @@ export default function RoleCreate({ q }: any) { setAttributes={setAttributes} bindings={uniqueRoleBindings} setBindings={setRoleBindings} + label="Create" cancel={() => resetAndClose()} submit={() => mutation()} loading={loading} diff --git a/assets/src/components/account/roles/RoleEdit.tsx b/assets/src/components/account/roles/RoleEdit.tsx index 39275a4659..e7d3556898 100644 --- a/assets/src/components/account/roles/RoleEdit.tsx +++ b/assets/src/components/account/roles/RoleEdit.tsx @@ -36,6 +36,7 @@ export default function RoleEdit({ role, open, setOpen }: any) { setAttributes={setAttributes} bindings={uniqueRoleBindings} setBindings={setRoleBindings} + label="Update" cancel={() => setOpen(false)} submit={() => mutation()} loading={loading} diff --git a/assets/src/components/account/roles/RoleForm.tsx b/assets/src/components/account/roles/RoleForm.tsx index bed75e5943..b1fe290b9c 100644 --- a/assets/src/components/account/roles/RoleForm.tsx +++ b/assets/src/components/account/roles/RoleForm.tsx @@ -43,6 +43,7 @@ const PermissionTypes = { } export default function RoleForm({ + label = 'Create', cancel, submit, loading, @@ -69,7 +70,7 @@ export default function RoleForm({ /> - {/* ROLE INFO */} + {/* Role info */} {step === 0 && ( )} - {/* BINDINGS */} + {/* Bindings */} {step === 1 && ( - Create + {label} )} From 6a8ce5b992f60c851fb5d7d34188c22e8bd12aca Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Fri, 3 Feb 2023 14:21:53 +0100 Subject: [PATCH 5/6] Add images to pod list --- assets/src/components/cluster/pods/Pods.tsx | 2 + .../src/components/cluster/pods/PodsList.tsx | 40 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/assets/src/components/cluster/pods/Pods.tsx b/assets/src/components/cluster/pods/Pods.tsx index 2b7837d798..c9afbaaf3f 100644 --- a/assets/src/components/cluster/pods/Pods.tsx +++ b/assets/src/components/cluster/pods/Pods.tsx @@ -36,6 +36,7 @@ import { ColActions, ColContainers, ColCpuReservation, + ColImages, ColMemoryReservation, ColNameLink, ColNamespace, @@ -115,6 +116,7 @@ export default function AllPods() { ColCpuReservation, ColRestarts, ColContainers, + ColImages, ColActions(refetch), ], [refetch]) diff --git a/assets/src/components/cluster/pods/PodsList.tsx b/assets/src/components/cluster/pods/PodsList.tsx index a44ab08104..330aa2ba6e 100644 --- a/assets/src/components/cluster/pods/PodsList.tsx +++ b/assets/src/components/cluster/pods/PodsList.tsx @@ -1,4 +1,9 @@ -import { A, Div, Flex } from 'honorable' +import { + A, + Div, + Flex, + Span, +} from 'honorable' import { Link, useNavigate } from 'react-router-dom' import { Row, createColumnHelper } from '@tanstack/react-table' import { @@ -94,6 +99,7 @@ type PodTableRow = { total?: number statuses?: ContainerStatus[] } + images?: string[] } const columnHelper = createColumnHelper() @@ -232,6 +238,34 @@ export const ColContainers = columnHelper.accessor(row => row?.containers?.statu header: 'Containers', }) +export const ColImages = columnHelper.accessor(row => row?.images || [], + { + id: 'images', + cell: props => { + const images = props.getValue() + + return images.map(image => ( + + + {image} + + + )) + }, + header: 'Images', + meta: { + truncate: true, + }, + }) + export const ColActions = refetch => columnHelper.display({ id: 'actions', cell: ({ row: { original } }: any) => ( @@ -298,6 +332,9 @@ export const PodsList = memo(({ && applications?.find(app => app?.name === pod.metadata.namespace) ?.spec?.descriptor?.icons?.[0] + const containerImages = pod?.spec?.containers?.map(container => container?.image) || [] + const initContainerImages = pod?.spec?.initContainers?.map(container => container?.image) || [] + return { name: pod?.metadata?.name, nodeName: pod?.spec?.nodeName || undefined, @@ -313,6 +350,7 @@ export const PodsList = memo(({ }, restarts: getRestarts(pod.status), containers: getPodContainersStats(pod.status), + images: [...new Set([...initContainerImages, ...containerImages])], } }), [applications, pods]) From 5b63b85ffda2ae25ed16dc94c3f9ffa81cc3f1ff Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Fri, 3 Feb 2023 14:30:04 +0100 Subject: [PATCH 6/6] Refactor --- assets/src/components/cluster/pods/PodsList.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/assets/src/components/cluster/pods/PodsList.tsx b/assets/src/components/cluster/pods/PodsList.tsx index 330aa2ba6e..9ad241815e 100644 --- a/assets/src/components/cluster/pods/PodsList.tsx +++ b/assets/src/components/cluster/pods/PodsList.tsx @@ -27,6 +27,8 @@ import { import { Confirm } from 'components/utils/Confirm' import { useMutation } from '@apollo/client' +import { isEmpty } from 'lodash' + import { LabelWithIcon, TableCaretLink, @@ -313,6 +315,14 @@ function getRestarts(status: Pod['status']) { 0) } +function getImages(containers): string[] { + return containers?.map(container => container?.image).filter(image => !isEmpty(image)) || [] +} + +function getPodImages(spec: Pod['spec']) { + return [...new Set([...getImages(spec?.containers), ...getImages(spec?.initContainers)])] +} + export const PodsList = memo(({ pods, applications, columns, ...props }: PodListProps) => { @@ -332,9 +342,6 @@ export const PodsList = memo(({ && applications?.find(app => app?.name === pod.metadata.namespace) ?.spec?.descriptor?.icons?.[0] - const containerImages = pod?.spec?.containers?.map(container => container?.image) || [] - const initContainerImages = pod?.spec?.initContainers?.map(container => container?.image) || [] - return { name: pod?.metadata?.name, nodeName: pod?.spec?.nodeName || undefined, @@ -350,7 +357,7 @@ export const PodsList = memo(({ }, restarts: getRestarts(pod.status), containers: getPodContainersStats(pod.status), - images: [...new Set([...initContainerImages, ...containerImages])], + images: getPodImages(pod.spec), } }), [applications, pods])