import React, { forwardRef, useEffect, useRef } from 'react'; import { Subuser } from '@/state/server/subusers'; import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { array, object, string } from 'yup'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import Field from '@/components/elements/Field'; import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import TitledGreyBox from '@/components/elements/TitledGreyBox'; import Checkbox from '@/components/elements/Checkbox'; import styled from 'styled-components/macro'; import createOrUpdateSubuser from '@/api/server/users/createOrUpdateSubuser'; import { ServerContext } from '@/state/server'; import { httpErrorToHuman } from '@/api/http'; import FlashMessageRender from '@/components/FlashMessageRender'; import Can from '@/components/elements/Can'; import { usePermissions } from '@/plugins/usePermissions'; import { useDeepMemo } from '@/plugins/useDeepMemo'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import Label from '@/components/elements/Label'; import Input from '@/components/elements/Input'; type Props = { subuser?: Subuser; } & RequiredModalProps; interface Values { email: string; permissions: string[]; } const PermissionLabel = styled.label` ${tw`flex items-center border border-transparent rounded p-2`}; text-transform: none; &:not(.disabled) { ${tw`cursor-pointer`}; &:hover { ${tw`border-neutral-500 bg-neutral-800`}; } } &.disabled { ${tw`opacity-50`}; & input[type="checkbox"]:not(:checked) { ${tw`border-0`}; } } `; const EditSubuserModal = forwardRef(({ subuser, ...props }, ref) => { const { values, isSubmitting, setFieldValue } = useFormikContext(); const [ canEditUser ] = usePermissions(subuser ? [ 'user.update' ] : [ 'user.create' ]); const permissions = useStoreState(state => state.permissions.data); const user = useStoreState(state => state.user.data!); // The currently logged in user's permissions. We're going to filter out any permissions // that they should not need. const loggedInPermissions = ServerContext.useStoreState(state => state.server.permissions); // The permissions that can be modified by this user. const editablePermissions = useDeepMemo(() => { const cleaned = Object.keys(permissions) .map(key => Object.keys(permissions[key].keys).map(pkey => `${key}.${pkey}`)); const list: string[] = ([] as string[]).concat.apply([], Object.values(cleaned)); if (user.rootAdmin || (loggedInPermissions.length === 1 && loggedInPermissions[0] === '*')) { return list; } return list.filter(key => loggedInPermissions.indexOf(key) >= 0); }, [ permissions, loggedInPermissions ]); return (

{subuser ? `${canEditUser ? 'Modify' : 'View'} permissions for ${subuser.email}` : 'Create new subuser' }

{(!user.rootAdmin && loggedInPermissions[0] !== '*') &&

Only permissions which your account is currently assigned may be selected when creating or modifying other users.

} {!subuser &&
}
{Object.keys(permissions).filter(key => key !== 'websocket').map((key, index) => (

{key}

{canEditUser && { if (e.currentTarget.checked) { setFieldValue('permissions', [ ...values.permissions, ...Object.keys(permissions[key].keys) .map(pkey => `${key}.${pkey}`) .filter(permission => values.permissions.indexOf(permission) === -1), ]); } else { setFieldValue('permissions', [ ...values.permissions.filter( permission => Object.keys(permissions[key].keys) .map(pkey => `${key}.${pkey}`) .indexOf(permission) < 0, ), ]); } }} /> }
} css={index > 0 ? tw`mt-4` : undefined} >

{permissions[key].description}

{Object.keys(permissions[key].keys).map((pkey, index) => ( 0 ? tw`mt-2` : undefined, ]} className={(!canEditUser || editablePermissions.indexOf(`${key}.${pkey}`) < 0) ? 'disabled' : undefined} >
{permissions[key].keys[pkey].length > 0 &&

{permissions[key].keys[pkey]}

}
))} ))}
); }); export default ({ subuser, ...props }: Props) => { const ref = useRef(null); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('user:edit'); createOrUpdateSubuser(uuid, values, subuser) .then(subuser => { appendSubuser(subuser); props.onDismissed(); }) .catch(error => { console.error(error); setSubmitting(false); addError({ key: 'user:edit', message: httpErrorToHuman(error) }); if (ref.current) { ref.current.scrollIntoView(); } }); }; useEffect(() => { clearFlashes('user:edit'); }, []); return (
); };