Local modifications to ClusterLabs/Anvil by Alteeve
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
7.9 KiB

import { FC, useMemo, useRef, useState } from 'react';
import API_BASE_URL from '../../lib/consts/API_BASE_URL';
import CommonUserInputGroup, {
INPUT_ID_USER_CONFIRM_PASSWORD,
INPUT_ID_USER_NAME,
INPUT_ID_USER_PASSWORD,
} from './CommonUserInputGroup';
import ConfirmDialog from '../ConfirmDialog';
import FormDialog from '../FormDialog';
import FormSummary from '../FormSummary';
import handleAPIError from '../../lib/handleAPIError';
import List from '../List';
import MessageBox, { Message } from '../MessageBox';
import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup';
import { ExpandablePanel } from '../Panels';
import periodicFetch from '../../lib/fetchers/periodicFetch';
import { BodyText } from '../Text';
import useChecklist from '../../hooks/useChecklist';
import useConfirmDialogProps from '../../hooks/useConfirmDialogProps';
import useFormUtils from '../../hooks/useFormUtils';
import useProtectedState from '../../hooks/useProtectedState';
const getFormEntries = (
...[{ target }]: DivFormEventHandlerParameters
): CreateUserRequestBody => {
const { elements } = target as HTMLFormElement;
const { value: userName } = elements.namedItem(
INPUT_ID_USER_NAME,
) as HTMLInputElement;
const inputUserPassword = elements.namedItem(INPUT_ID_USER_PASSWORD);
let password = '';
if (inputUserPassword) {
({ value: password } = inputUserPassword as HTMLInputElement);
}
return { password, userName };
};
const ManageUsersForm: FC = () => {
const addUserFormDialogRef = useRef<ConfirmDialogForwardedRefContent>({});
const confirmDialogRef = useRef<ConfirmDialogForwardedRefContent>({});
const editUserFormDialogRef = useRef<ConfirmDialogForwardedRefContent>({});
const messageGroupRef = useRef<MessageGroupForwardedRefContent>({});
const [confirmDialogProps, setConfirmDialogProps] = useConfirmDialogProps();
const [editUsers, setEditUsers] = useState<boolean>(false);
const [listMessage, setListMessage] = useProtectedState<Message>({
children: `No users found.`,
});
const [userDetail, setUserDetail] = useProtectedState<
UserOverviewMetadata | undefined
>(undefined);
const { data: users, isLoading: loadingUsers } =
periodicFetch<UserOverviewMetadataList>(`${API_BASE_URL}/user`, {
onError: (error) => {
setListMessage(handleAPIError(error));
},
});
const formUtils = useFormUtils(
[
INPUT_ID_USER_CONFIRM_PASSWORD,
INPUT_ID_USER_NAME,
INPUT_ID_USER_PASSWORD,
],
messageGroupRef,
);
const { isFormInvalid, isFormSubmitting, submitForm } = formUtils;
const { buildDeleteDialogProps, checks, getCheck, hasChecks, setCheck } =
useChecklist({ list: users });
const { userName: udetailName, userUUID: udetailUuid } = useMemo<
Partial<UserOverviewMetadata>
>(() => userDetail ?? {}, [userDetail]);
const addUserFormDialogProps = useMemo<ConfirmDialogProps>(
() => ({
actionProceedText: 'Add',
content: (
<CommonUserInputGroup
formUtils={formUtils}
requirePassword
showPasswordField
/>
),
onSubmitAppend: (...args) => {
const body = getFormEntries(...args);
setConfirmDialogProps({
actionProceedText: 'Add',
content: <FormSummary entries={body} hasPassword />,
onProceedAppend: () => {
submitForm({
body,
getErrorMsg: (parentMsg) => <>Add user failed. {parentMsg}</>,
method: 'post',
successMsg: `Created user ${body.userName}.`,
url: '/user',
});
},
titleText: `Add the following new user?`,
});
confirmDialogRef.current.setOpen?.call(null, true);
},
titleText: 'Add a web interface user',
}),
[formUtils, setConfirmDialogProps, submitForm],
);
const editUserFormDialogProps = useMemo<ConfirmDialogProps>(
() => ({
actionProceedText: 'Edit',
content: (
<CommonUserInputGroup
formUtils={formUtils}
previous={{ name: udetailName }}
readOnlyUserName={udetailName === 'admin'}
showPasswordField
/>
),
onSubmitAppend: (...args) => {
const body = getFormEntries(...args);
setConfirmDialogProps({
actionProceedText: 'Update',
content: <FormSummary entries={body} hasPassword />,
onProceedAppend: () => {
submitForm({
body,
getErrorMsg: (parentMsg) => <>Update user failed. {parentMsg}</>,
method: 'put',
successMsg: `Updated user ${udetailName}`,
url: `/user/${udetailUuid}`,
});
},
titleText: `Update user ${udetailName} with the following?`,
});
confirmDialogRef.current.setOpen?.call(null, true);
},
titleText: `Edit user ${udetailName}`,
}),
[formUtils, setConfirmDialogProps, submitForm, udetailName, udetailUuid],
);
const messageArea = useMemo(
() => (
<MessageGroup
count={1}
defaultMessageType="warning"
ref={messageGroupRef}
/>
),
[],
);
const allowModOthers = useMemo<boolean>(
() => users?.current?.userName === 'admin',
[users],
);
return (
<>
<ExpandablePanel header="Manage users" loading={loadingUsers}>
<List
allowAddItem={allowModOthers}
allowDelete={allowModOthers}
allowEdit
allowItemButton={editUsers}
disableDelete={!hasChecks}
edit={editUsers}
getListItemCheckboxProps={(key, { userName }) => ({
disabled: userName === 'admin',
})}
header
listEmpty={<MessageBox {...listMessage} />}
listItems={users}
onAdd={() => {
addUserFormDialogRef.current.setOpen?.call(null, true);
}}
onDelete={() => {
setConfirmDialogProps(
buildDeleteDialogProps({
confirmDialogProps: {
onProceedAppend: () => {
submitForm({
body: { uuids: checks },
getErrorMsg: (parentMsg) => (
<>Delete user(s) failed. {parentMsg}</>
),
method: 'delete',
url: '/user',
});
},
},
formSummaryProps: {
renderEntry: ({ key }) => (
<BodyText>{users?.[key].userName}</BodyText>
),
},
getConfirmDialogTitle: (length) =>
`Delete the following ${length} users?`,
}),
);
confirmDialogRef.current.setOpen?.call(null, true);
}}
onEdit={() => setEditUsers((previous) => !previous)}
onItemCheckboxChange={(key, event, checked) => setCheck(key, checked)}
onItemClick={(value) => {
if (editUsers) {
setUserDetail(value);
editUserFormDialogRef.current.setOpen?.call(null, true);
}
}}
renderListItemCheckboxState={(key) => getCheck(key)}
renderListItem={(userUUID, { userName }) => (
<BodyText>{userName}</BodyText>
)}
/>
</ExpandablePanel>
<FormDialog
{...addUserFormDialogProps}
disableProceed={isFormInvalid}
loadingAction={isFormSubmitting}
preActionArea={messageArea}
ref={addUserFormDialogRef}
/>
<FormDialog
{...editUserFormDialogProps}
disableProceed={isFormInvalid}
loadingAction={isFormSubmitting}
preActionArea={messageArea}
ref={editUserFormDialogRef}
/>
<ConfirmDialog
closeOnProceed
{...confirmDialogProps}
ref={confirmDialogRef}
/>
</>
);
};
export default ManageUsersForm;