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.
381 lines
11 KiB
381 lines
11 KiB
import { Grid, menuClasses as muiMenuClasses } from '@mui/material'; |
|
import { AxiosError } from 'axios'; |
|
import { FC, useMemo } from 'react'; |
|
import { v4 as uuidv4 } from 'uuid'; |
|
|
|
import ActionGroup from '../ActionGroup'; |
|
import api from '../../lib/api'; |
|
import FlexBox from '../FlexBox'; |
|
import FormSummary from '../FormSummary'; |
|
import handleAPIError from '../../lib/handleAPIError'; |
|
import mailRecipientListSchema from './schema'; |
|
import ManageAlertOverride from './ManageAlertOverride'; |
|
import MessageGroup from '../MessageGroup'; |
|
import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; |
|
import SelectWithLabel from '../SelectWithLabel'; |
|
import { BodyText, SmallText } from '../Text'; |
|
import UncontrolledInput from '../UncontrolledInput'; |
|
import useFormikUtils from '../../hooks/useFormikUtils'; |
|
|
|
/** |
|
* TODO: add descriptions to each item: |
|
* |
|
=head4 1 / critical |
|
|
|
Alerts at this level will go to all recipients, except for those ignoring the source system entirely. |
|
|
|
This is reserved for alerts that could lead to imminent service interruption or unexpected loss of redundancy. |
|
|
|
Alerts at this level should trigger alarm systems for all administrators as well as management who may be impacted by service interruptions. |
|
|
|
=head4 2 / warning |
|
|
|
This is used for alerts that require attention from administrators. Examples include intentional loss of redundancy caused by load shedding, hardware in pre-failure, loss of input power, temperature anomalies, etc. |
|
|
|
Alerts at this level should trigger alarm systems for administrative staff. |
|
|
|
=head4 3 / notice |
|
|
|
This is used for alerts that are generally safe to ignore, but might provide early warnings of developing issues or insight into system behaviour. |
|
|
|
Alerts at this level should not trigger alarm systems. Periodic review is sufficient. |
|
|
|
=head4 4 / info |
|
|
|
This is used for alerts that are almost always safe to ignore, but may be useful in testing and debugging. |
|
* |
|
*/ |
|
const LEVEL_OPTIONS: SelectItem<number>[] = [ |
|
{ |
|
displayValue: ( |
|
<FlexBox spacing={0}> |
|
<BodyText inheritColour fontWeight="inherit"> |
|
Critical |
|
</BodyText> |
|
<SmallText inheritColour whiteSpace="normal"> |
|
Alerts that could lead to imminent service interruption or unexpected |
|
loss of redundancy. |
|
</SmallText> |
|
</FlexBox> |
|
), |
|
value: 1, |
|
}, |
|
{ |
|
displayValue: ( |
|
<FlexBox spacing={0}> |
|
<BodyText inheritColour fontWeight="inherit"> |
|
Warning |
|
</BodyText> |
|
<SmallText inheritColour whiteSpace="normal"> |
|
Alerts that require attention from administrators, such as redundancy |
|
loss due to load shedding, hardware in pre-failure, input power loss, |
|
temperature anomalies, etc. |
|
</SmallText> |
|
</FlexBox> |
|
), |
|
value: 2, |
|
}, |
|
{ |
|
displayValue: ( |
|
<FlexBox spacing={0}> |
|
<BodyText inheritColour fontWeight="inherit"> |
|
Notice |
|
</BodyText> |
|
<SmallText inheritColour whiteSpace="normal"> |
|
Alerts that are generally safe to ignore, but might provide early |
|
warnings of developing issues or insight into system behaviour. |
|
</SmallText> |
|
</FlexBox> |
|
), |
|
value: 3, |
|
}, |
|
{ |
|
displayValue: ( |
|
<FlexBox spacing={0}> |
|
<BodyText inheritColour fontWeight="inherit"> |
|
Info |
|
</BodyText> |
|
<SmallText inheritColour whiteSpace="normal"> |
|
Alerts that are almost always safe to ignore, but may be useful in |
|
testing and debugging. |
|
</SmallText> |
|
</FlexBox> |
|
), |
|
value: 4, |
|
}, |
|
]; |
|
|
|
const MAP_TO_LEVEL_LABEL: Record<number, string> = { |
|
1: 'Critical', |
|
2: 'Warning', |
|
3: 'Notice', |
|
4: 'Info', |
|
}; |
|
|
|
const getAlertOverrideRequestList = ( |
|
current: MailRecipientFormikMailRecipient, |
|
initial?: MailRecipientFormikMailRecipient, |
|
urlPrefix = '/alert-override', |
|
): AlertOverrideRequest[] => { |
|
const { uuid: mailRecipientUuid } = current; |
|
|
|
if (!mailRecipientUuid) return []; |
|
|
|
return Object.values(current.alertOverrides).reduce<AlertOverrideRequest[]>( |
|
(previous, { remove, level, target, uuids: existingOverrides }) => { |
|
/** |
|
* There's no update, just delete every record and create the new records. |
|
* |
|
* This is not optimal, but keep it until there's a better solution. |
|
*/ |
|
|
|
if (existingOverrides) { |
|
previous.push( |
|
...Object.keys(existingOverrides).map<AlertOverrideRequest>( |
|
(overrideUuid) => ({ |
|
method: 'delete', |
|
url: `${urlPrefix}/${overrideUuid}`, |
|
}), |
|
), |
|
); |
|
} |
|
|
|
if (target && !remove) { |
|
const newHosts: string[] = target.subnodes ?? [target.uuid]; |
|
|
|
previous.push( |
|
...newHosts.map<AlertOverrideRequest>((hostUuid) => ({ |
|
body: { hostUuid, level, mailRecipientUuid }, |
|
method: 'post', |
|
url: urlPrefix, |
|
})), |
|
); |
|
} |
|
|
|
return previous; |
|
}, |
|
[], |
|
); |
|
}; |
|
|
|
const AddMailRecipientForm: FC<AddMailRecipientFormProps> = (props) => { |
|
const { |
|
alertOverrideTargetOptions, |
|
mailRecipientUuid, |
|
previousFormikValues, |
|
tools, |
|
} = props; |
|
|
|
const mrUuid = useMemo<string>( |
|
() => mailRecipientUuid ?? uuidv4(), |
|
[mailRecipientUuid], |
|
); |
|
|
|
const formikUtils = useFormikUtils<MailRecipientFormikValues>({ |
|
initialValues: previousFormikValues ?? { |
|
[mrUuid]: { |
|
alertOverrides: {}, |
|
email: '', |
|
language: 'en_CA', |
|
level: 2, |
|
name: '', |
|
}, |
|
}, |
|
onSubmit: (values, { setSubmitting }) => { |
|
const { [mrUuid]: mailRecipient } = values; |
|
const { confirm } = tools; |
|
|
|
let actionProceedText: string = 'Add'; |
|
let errorMessage: React.ReactNode = <>Failed to add mail recipient.</>; |
|
let method: 'post' | 'put' = 'post'; |
|
let successMessage: React.ReactNode = <>Mail recipient added.</>; |
|
let titleText: string = `Add mail recipient with the following?`; |
|
let url: string = '/mail-recipient'; |
|
|
|
if (previousFormikValues) { |
|
actionProceedText = 'Update'; |
|
errorMessage = <>Failed to update mail server.</>; |
|
method = 'put'; |
|
successMessage = <>Mail recipient updated.</>; |
|
titleText = `Update ${mailRecipient.name} with the following?`; |
|
url += `/${mrUuid}`; |
|
} |
|
|
|
const { alertOverrides, uuid: ignore, ...mrBody } = mailRecipient; |
|
|
|
confirm.prepare({ |
|
actionProceedText, |
|
content: ( |
|
<> |
|
<FormSummary entries={mrBody} /> |
|
<FormSummary |
|
entries={{ |
|
alertOverrides: Object.entries(alertOverrides).reduce< |
|
Record<string, { level: number; name: string }> |
|
>((previous, [valueId, value]) => { |
|
if (value.remove || !value.target) return previous; |
|
|
|
previous[valueId] = { |
|
level: value.level, |
|
name: value.target.name, |
|
}; |
|
|
|
return previous; |
|
}, {}), |
|
}} |
|
/> |
|
</> |
|
), |
|
onCancelAppend: () => setSubmitting(false), |
|
onProceedAppend: async () => { |
|
confirm.loading(true); |
|
|
|
const handleError = (error: AxiosError) => { |
|
const emsg = handleAPIError(error); |
|
|
|
emsg.children = ( |
|
<> |
|
{errorMessage} {emsg.children} |
|
</> |
|
); |
|
|
|
confirm.finish('Error', emsg); |
|
}; |
|
|
|
// Handle the mail recipient first, wait until it's done to process |
|
// the related alert override records. |
|
|
|
api[method](url, mrBody) |
|
.then((response) => { |
|
const { data } = response; |
|
|
|
const shallow = { ...mailRecipient }; |
|
|
|
if (data) { |
|
shallow.uuid = data.uuid; |
|
} |
|
|
|
const initial = |
|
previousFormikValues && previousFormikValues[mrUuid]; |
|
|
|
const promises = getAlertOverrideRequestList( |
|
shallow, |
|
initial, |
|
).map((request) => |
|
api[request.method](request.url, request.body), |
|
); |
|
|
|
Promise.all(promises) |
|
.then(() => |
|
confirm.finish('Success', { children: successMessage }), |
|
) |
|
.catch(handleError); |
|
}) |
|
.catch(handleError); |
|
}, |
|
titleText, |
|
}); |
|
|
|
confirm.open(true); |
|
}, |
|
validationSchema: mailRecipientListSchema, |
|
}); |
|
|
|
const { disabledSubmit, formik, formikErrors, handleChange } = formikUtils; |
|
|
|
const emailChain = useMemo<string>(() => `${mrUuid}.email`, [mrUuid]); |
|
const levelChain = useMemo<string>(() => `${mrUuid}.level`, [mrUuid]); |
|
const nameChain = useMemo<string>(() => `${mrUuid}.name`, [mrUuid]); |
|
|
|
return ( |
|
<Grid |
|
columns={{ xs: 1, sm: 2 }} |
|
component="form" |
|
container |
|
onSubmit={(event) => { |
|
event.preventDefault(); |
|
|
|
formik.submitForm(); |
|
}} |
|
spacing="1em" |
|
> |
|
<Grid item xs={1}> |
|
<UncontrolledInput |
|
input={ |
|
<OutlinedInputWithLabel |
|
id={nameChain} |
|
label="Recipient name" |
|
name={nameChain} |
|
onChange={handleChange} |
|
required |
|
value={formik.values[mrUuid].name} |
|
/> |
|
} |
|
/> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<UncontrolledInput |
|
input={ |
|
<OutlinedInputWithLabel |
|
id={emailChain} |
|
label="Recipient email" |
|
name={emailChain} |
|
onChange={handleChange} |
|
required |
|
value={formik.values[mrUuid].email} |
|
/> |
|
} |
|
/> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<UncontrolledInput |
|
input={ |
|
<SelectWithLabel |
|
id={levelChain} |
|
label="Alert level" |
|
name={levelChain} |
|
onChange={formik.handleChange} |
|
required |
|
selectItems={LEVEL_OPTIONS} |
|
selectProps={{ |
|
MenuProps: { |
|
sx: { |
|
[`& .${muiMenuClasses.paper}`]: { |
|
maxWidth: { md: '60%', lg: '40%' }, |
|
}, |
|
}, |
|
}, |
|
renderValue: (value) => MAP_TO_LEVEL_LABEL[value], |
|
}} |
|
value={formik.values[mrUuid].level} |
|
/> |
|
} |
|
/> |
|
</Grid> |
|
<Grid item width="100%"> |
|
<ManageAlertOverride |
|
alertOverrideTargetOptions={alertOverrideTargetOptions} |
|
formikUtils={formikUtils} |
|
mailRecipientUuid={mrUuid} |
|
/> |
|
</Grid> |
|
<Grid item width="100%"> |
|
<MessageGroup count={1} messages={formikErrors} /> |
|
</Grid> |
|
<Grid item width="100%"> |
|
<ActionGroup |
|
actions={[ |
|
{ |
|
background: 'blue', |
|
children: previousFormikValues ? 'Update' : 'Add', |
|
disabled: disabledSubmit, |
|
type: 'submit', |
|
}, |
|
]} |
|
/> |
|
</Grid> |
|
</Grid> |
|
); |
|
}; |
|
|
|
export default AddMailRecipientForm;
|
|
|