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/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} diff --git a/assets/src/components/account/roles/RoleCreate.tsx b/assets/src/components/account/roles/RoleCreate.tsx index 6476419fc1..97deefb7cf 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,20 @@ 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..e7d3556898 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,16 @@ 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..b1fe290b9c 100644 --- a/assets/src/components/account/roles/RoleForm.tsx +++ b/assets/src/components/account/roles/RoleForm.tsx @@ -1,96 +1,171 @@ -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 RoleFormBindings from './RoleFormBindings' 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({ + label = 'Create', + 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 ( - + + + + + {/* Role info */} + {step === 0 && ( + + setAttributes({ ...attributes, name: value })} + /> + setAttributes({ ...attributes, description: value })} + /> +

+ Permissions +

+
+ {permissions.map(([perm, description], i) => ( + + ))} +
+
+ )} + + {/* Bindings */} + {step === 1 && ( + + )} + {error && ( )} - setView(key as string), - }} + + - {Object.entries(TABS).map(([key, { label }]) => ( - {label} - ))} - - - {view === 'General' && ( - + {step === 0 && ( + <> + + + )} - {view === 'Permissions' && ( - - - Permissions - - Grant permissions to all users and groups bound to this role - - - - {permissions.map(([perm, description], i) => ( - - ))} - - + + {step === 1 && ( + <> + + + )} - -
+ + ) } diff --git a/assets/src/components/account/roles/RoleFormGeneralAttributes.tsx b/assets/src/components/account/roles/RoleFormBindings.tsx similarity index 72% rename from assets/src/components/account/roles/RoleFormGeneralAttributes.tsx rename to assets/src/components/account/roles/RoleFormBindings.tsx index 6dc2fc034c..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, @@ -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)} /> -
+ ) } 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..9ad241815e 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 { @@ -22,6 +27,8 @@ import { import { Confirm } from 'components/utils/Confirm' import { useMutation } from '@apollo/client' +import { isEmpty } from 'lodash' + import { LabelWithIcon, TableCaretLink, @@ -94,6 +101,7 @@ type PodTableRow = { total?: number statuses?: ContainerStatus[] } + images?: string[] } const columnHelper = createColumnHelper() @@ -232,6 +240,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) => ( @@ -279,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) => { @@ -313,6 +357,7 @@ export const PodsList = memo(({ }, restarts: getRestarts(pod.status), containers: getPodContainersStats(pod.status), + images: getPodImages(pod.spec), } }), [applications, pods])