diff --git a/packages/tapisui-hooks/src/pods/index.ts b/packages/tapisui-hooks/src/pods/index.ts index 01373ff3..c9fa7154 100644 --- a/packages/tapisui-hooks/src/pods/index.ts +++ b/packages/tapisui-hooks/src/pods/index.ts @@ -5,6 +5,7 @@ export { default as useGetVolume } from './useGetVolume'; export { default as useListVolumeFiles } from './useListVolumeFiles'; export { default as useUpdateTemplate } from './useUpdateTemplate'; export { default as useDeletePodPermission } from './useDeletePodPermission'; +export { default as useSetPodPermission } from './useSetPodPermission'; export { default as useGetPodLogs } from './useGetPodLogs'; export { default as useListImages } from './useListImages'; export { default as useListVolumes } from './useListVolumes'; diff --git a/packages/tapisui-hooks/src/pods/useSetPodPermission.ts b/packages/tapisui-hooks/src/pods/useSetPodPermission.ts new file mode 100644 index 00000000..e73295aa --- /dev/null +++ b/packages/tapisui-hooks/src/pods/useSetPodPermission.ts @@ -0,0 +1,52 @@ +import { useEffect } from 'react'; +import { useMutation, MutateOptions } from 'react-query'; +import { Pods } from '@tapis/tapis-typescript'; +import { Pods as API } from '@tapis/tapisui-api'; +import { useTapisConfig } from '../context'; +import QueryKeys from './queryKeys'; + +type SetPodPermissionHookParams = Pods.SetPodPermissionRequest; + +const useSetPodPermission = (podId: string) => { + const { basePath, accessToken } = useTapisConfig(); + const jwt = accessToken?.access_token || ''; + + // The useMutation react-query hook is used to call operations that make server-side changes + // (Other hooks would be used for data retrieval) + // + // In this case, mkdir helper is called to perform the operation + const { mutate, isLoading, isError, isSuccess, data, error, reset } = + useMutation< + Pods.PodPermissionsResponse, + Error, + Pods.SetPodPermissionRequest + >([QueryKeys.setPodPermission, podId, basePath, jwt], (params) => + API.setPodPermission(params, basePath, jwt) + ); + + useEffect(() => reset(), [reset, podId]); + + // Return hook object with loading states and login function + return { + isLoading, + isError, + isSuccess, + data, + error, + reset, + setPodPermission: ( + params: SetPodPermissionHookParams, + // react-query options to allow callbacks such as onSuccess + options?: MutateOptions< + Pods.PodPermissionsResponse, + Error, + SetPodPermissionHookParams + > + ) => { + // Call mutate to trigger a single post-like API operation + return mutate(params, options); + }, + }; +}; + +export default useSetPodPermission; diff --git a/src/app/Pods/_components/Modals/DeleteVolumeModal/DeleteVolumeModal.tsx b/src/app/Pods/_components/Modals/DeleteVolumeModal/DeleteVolumeModal.tsx index 3b773eef..1ea93c75 100644 --- a/src/app/Pods/_components/Modals/DeleteVolumeModal/DeleteVolumeModal.tsx +++ b/src/app/Pods/_components/Modals/DeleteVolumeModal/DeleteVolumeModal.tsx @@ -10,17 +10,25 @@ import styles from './DeletePodModal.module.scss'; import * as Yup from 'yup'; import { useQueryClient } from 'react-query'; import { Pods as Hooks } from '@tapis/tapisui-hooks'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useHistory } from 'react-router-dom'; + +import { useDispatch } from 'react-redux'; +import { setVolumeRootTab } from '../../../redux/podsSlice'; const DeleteVolumeModal: React.FC = ({ toggle }) => { const { data } = Hooks.useListVolumes(); //{search: `owner.like.${''}`,} const volumes: Array = data?.result ?? []; - //Allows the pod list to update without the user having to refresh the page + // Allows the pod list to update without the user having to refresh the page const queryClient = useQueryClient(); + const history = useHistory(); + const dispatch = useDispatch(); + const onSuccess = useCallback(() => { queryClient.invalidateQueries(Hooks.queryKeys.listVolumes); - }, [queryClient]); + history.push('/pods/volumes'); + dispatch(setVolumeRootTab('dashboard')); // Ensure setVolumeRootTab is available in the scope + }, [queryClient, history]); const { deleteVolume, isLoading, error, isSuccess, reset } = Hooks.useDeleteVolume(); diff --git a/src/app/Pods/_components/Modals/PodPermissionModal/PodPermissionModal.module.scss b/src/app/Pods/_components/Modals/PodPermissionModal/PodPermissionModal.module.scss new file mode 100644 index 00000000..04ba38e9 --- /dev/null +++ b/src/app/Pods/_components/Modals/PodPermissionModal/PodPermissionModal.module.scss @@ -0,0 +1,16 @@ +.modal-settings { + max-height: 38rem; + overflow-y: scroll; +} + +.item { + border: 1px dotted lightgray; + padding: 0.5em 0.5em 0.5em 0.5em; + margin-bottom: 1em; +} + +.array { + border: 1px solid gray; + padding: 0.5em 0.5em 0.5em 0.5em; + margin-bottom: 0.5em; +} diff --git a/src/app/Pods/_components/Modals/PodPermissionModal/PodPermissionModal.tsx b/src/app/Pods/_components/Modals/PodPermissionModal/PodPermissionModal.tsx new file mode 100644 index 00000000..a959dcde --- /dev/null +++ b/src/app/Pods/_components/Modals/PodPermissionModal/PodPermissionModal.tsx @@ -0,0 +1,152 @@ +import { Button } from 'reactstrap'; +import { Pods } from '@tapis/tapis-typescript'; +import { GenericModal } from '@tapis/tapisui-common'; +import { SubmitWrapper } from '@tapis/tapisui-common'; +import { ToolbarModalProps } from '../ToolbarModalProps'; +import { Form, Formik } from 'formik'; +import { FormikSelect, FormikInput } from '@tapis/tapisui-common'; +import { useEffect, useCallback } from 'react'; +import styles from './PodPermissionModal.module.scss'; +import * as Yup from 'yup'; +import { useQueryClient } from 'react-query'; +import { Pods as Hooks } from '@tapis/tapisui-hooks'; +import { useLocation, useHistory } from 'react-router-dom'; + +import { useDispatch } from 'react-redux'; +import { setVolumeRootTab } from '../../../redux/podsSlice'; + +const PodPermissionModal: React.FC = ({ toggle }) => { + const { data } = Hooks.useListPods(); // Assuming there's a hook to list pods + const pods: Array = data?.result ?? []; + + const queryClient = useQueryClient(); + const history = useHistory(); + const dispatch = useDispatch(); + const location = useLocation(); + const podIdFromLocation = location.pathname.split('/')[3] || ''; + + const onSuccess = useCallback(() => { + queryClient.invalidateQueries(Hooks.queryKeys.getPodPermissions); + }, [queryClient, history]); + + const { setPodPermission, isLoading, error, isSuccess, reset } = + Hooks.useSetPodPermission(podIdFromLocation); + + useEffect(() => { + reset(); + }, [reset]); + + const validationSchema = Yup.object({ + podId: Yup.string().required('Pod ID is required'), + username: Yup.string().required('Username is required'), + permissionLevel: Yup.string().required('Permission level is required'), + }); + + + const initialValues = { + podId: podIdFromLocation, + username: '', + permissionLevel: '', + }; + + const onSubmit = ( + { podId, username, permissionLevel }: { podId: string; username: string; permissionLevel: string }, + { + setFieldValue, + resetForm, + setTouched, + }: { + setFieldValue: (field: string, value: any) => void; + resetForm: () => void; + setTouched: (touched: { [field: string]: boolean }) => void; + } + ) => { + setPodPermission({ podId, setPermission: { user: username, level: permissionLevel } }, { onSuccess }); + resetForm(); + setFieldValue('podId', ''); + setTouched({ + podId: false, + username: false, + permissionLevel: false, + }); + }; + + return ( + + + {() => ( +
+ + + {pods.length ? ( + pods.map((pod) => { + return ( + + ); + }) + ) : ( + No pods found + )} + + + + + )} +
+ + } + footer={ + + + + } + /> + ); +}; + +export default PodPermissionModal; \ No newline at end of file diff --git a/src/app/Pods/_components/Modals/PodPermissionModal/index.ts b/src/app/Pods/_components/Modals/PodPermissionModal/index.ts new file mode 100644 index 00000000..7158729e --- /dev/null +++ b/src/app/Pods/_components/Modals/PodPermissionModal/index.ts @@ -0,0 +1,3 @@ +import PodPermissionModal from './PodPermissionModal'; + +export default PodPermissionModal; diff --git a/src/app/Pods/_components/Modals/index.ts b/src/app/Pods/_components/Modals/index.ts index bcbb0e43..6ab297d1 100644 --- a/src/app/Pods/_components/Modals/index.ts +++ b/src/app/Pods/_components/Modals/index.ts @@ -1 +1,2 @@ export { default as DeleteVolumeModal } from './DeleteVolumeModal'; +export { default as PodPermissionModal } from './PodPermissionModal'; diff --git a/src/app/Pods/_components/PagePods/PagePods.tsx b/src/app/Pods/_components/PagePods/PagePods.tsx index 4a19fb8b..6fe8f528 100644 --- a/src/app/Pods/_components/PagePods/PagePods.tsx +++ b/src/app/Pods/_components/PagePods/PagePods.tsx @@ -25,6 +25,7 @@ import { QueryWrapper, SectionMessage, } from '@tapis/tapisui-common'; +import { PodPermissionModal } from '../Modals'; import { NavPods, PodsCodeMirror, PodsNavigation } from 'app/Pods/_components'; import PodToolbar from 'app/Pods/_components/PodToolbar'; @@ -298,6 +299,20 @@ const PagePods: React.FC<{ objId: string | undefined }> = ({ objId }) => { )} + {podTab === 'perms' && ( + + )} {rightButtons.map( ({ id, label, tabValue, customOnClick, icon, disabled }) => (