Update UI to support setting "Continue on Error" for tasks
This commit is contained in:
parent
92cd659db3
commit
ea057cb1cb
|
@ -5,15 +5,16 @@ interface Data {
|
||||||
action: string;
|
action: string;
|
||||||
payload: string;
|
payload: string;
|
||||||
timeOffset: string | number;
|
timeOffset: string | number;
|
||||||
|
continueOnFailure: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (uuid: string, schedule: number, task: number | undefined, { timeOffset, ...data }: Data): Promise<Task> => {
|
export default async (uuid: string, schedule: number, task: number | undefined, data: Data): Promise<Task> => {
|
||||||
return new Promise((resolve, reject) => {
|
const { data: response } = await http.post(`/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, {
|
||||||
http.post(`/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, {
|
action: data.action,
|
||||||
...data,
|
payload: data.payload,
|
||||||
time_offset: timeOffset,
|
continue_on_failure: data.continueOnFailure,
|
||||||
})
|
time_offset: data.timeOffset,
|
||||||
.then(({ data }) => resolve(rawDataToServerTask(data.attributes)))
|
|
||||||
.catch(reject);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return rawDataToServerTask(response.attributes);
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,15 +104,15 @@ const EditScheduleModal = ({ schedule }: Props) => {
|
||||||
</p>
|
</p>
|
||||||
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
|
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
|
||||||
<FormikSwitch
|
<FormikSwitch
|
||||||
name={'only_when_online'}
|
name={'onlyWhenOnline'}
|
||||||
description={'If disabled this schedule will always run, regardless of the server\'s current power state.'}
|
description={'Only execute this schedule when the server is in a running state.'}
|
||||||
label={'Only When Server Is Online'}
|
label={'Only When Server Is Online'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
|
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
|
||||||
<FormikSwitch
|
<FormikSwitch
|
||||||
name={'enabled'}
|
name={'enabled'}
|
||||||
description={'If disabled this schedule and it\'s associated tasks will not run.'}
|
description={'This schedule will be executed automatically if enabled.'}
|
||||||
label={'Schedule Enabled'}
|
label={'Schedule Enabled'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,12 +13,7 @@ export default ({ schedule }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{visible &&
|
<TaskDetailsModal schedule={schedule} visible={visible} onModalDismissed={() => setVisible(false)}/>
|
||||||
<TaskDetailsModal
|
|
||||||
schedule={schedule}
|
|
||||||
onDismissed={() => setVisible(false)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<Button onClick={() => setVisible(true)} css={tw`flex-1`}>
|
<Button onClick={() => setVisible(true)} css={tw`flex-1`}>
|
||||||
New Task
|
New Task
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default () => {
|
||||||
}
|
}
|
||||||
<Can action={'schedule.create'}>
|
<Can action={'schedule.create'}>
|
||||||
<div css={tw`mt-8 flex justify-end`}>
|
<div css={tw`mt-8 flex justify-end`}>
|
||||||
{visible && <EditScheduleModal appear visible onDismissed={() => setVisible(false)}/>}
|
<EditScheduleModal visible={visible} onModalDismissed={() => setVisible(false)}/>
|
||||||
<Button type={'button'} onClick={() => setVisible(true)}>
|
<Button type={'button'} onClick={() => setVisible(true)}>
|
||||||
Create schedule
|
Create schedule
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -153,7 +153,7 @@ export default () => {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EditScheduleModal visible={showEditModal} schedule={schedule} onDismissed={toggleEditModal}/>
|
<EditScheduleModal visible={showEditModal} schedule={schedule} onModalDismissed={toggleEditModal}/>
|
||||||
<div css={tw`mt-6 flex sm:justify-end`}>
|
<div css={tw`mt-6 flex sm:justify-end`}>
|
||||||
<Can action={'schedule.delete'}>
|
<Can action={'schedule.delete'}>
|
||||||
<DeleteScheduleButton
|
<DeleteScheduleButton
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Schedule, Task } from '@/api/server/schedules/getServerSchedules';
|
import { Schedule, Task } from '@/api/server/schedules/getServerSchedules';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faClock, faCode, faFileArchive, faPencilAlt, faToggleOn, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
import {
|
||||||
|
faArrowCircleDown,
|
||||||
|
faClock,
|
||||||
|
faCode,
|
||||||
|
faFileArchive,
|
||||||
|
faPencilAlt,
|
||||||
|
faToggleOn,
|
||||||
|
faTrashAlt,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask';
|
import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
@ -59,11 +67,12 @@ export default ({ schedule, task }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div css={tw`sm:flex items-center p-3 sm:p-6 border-b border-neutral-800`}>
|
<div css={tw`sm:flex items-center p-3 sm:p-6 border-b border-neutral-800`}>
|
||||||
<SpinnerOverlay visible={isLoading} fixed size={'large'}/>
|
<SpinnerOverlay visible={isLoading} fixed size={'large'}/>
|
||||||
{isEditing && <TaskDetailsModal
|
<TaskDetailsModal
|
||||||
schedule={schedule}
|
schedule={schedule}
|
||||||
task={task}
|
task={task}
|
||||||
onDismissed={() => setIsEditing(false)}
|
visible={isEditing}
|
||||||
/>}
|
onModalDismissed={() => setIsEditing(false)}
|
||||||
|
/>
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
title={'Confirm task deletion'}
|
title={'Confirm task deletion'}
|
||||||
buttonText={'Delete Task'}
|
buttonText={'Delete Task'}
|
||||||
|
@ -89,6 +98,14 @@ export default ({ schedule, task }: Props) => {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`mt-3 sm:mt-0 flex items-center w-full sm:w-auto`}>
|
<div css={tw`mt-3 sm:mt-0 flex items-center w-full sm:w-auto`}>
|
||||||
|
{task.continueOnFailure &&
|
||||||
|
<div css={tw`mr-6`}>
|
||||||
|
<div css={tw`flex items-center px-2 py-1 bg-yellow-500 text-yellow-800 text-sm rounded-full`}>
|
||||||
|
<Icon icon={faArrowCircleDown} css={tw`w-3 h-3 mr-2`}/>
|
||||||
|
Continues on Failure
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{task.sequenceId > 1 && task.timeOffset > 0 &&
|
{task.sequenceId > 1 && task.timeOffset > 0 &&
|
||||||
<div css={tw`mr-6`}>
|
<div css={tw`mr-6`}>
|
||||||
<div css={tw`flex items-center px-2 py-1 bg-neutral-500 text-sm rounded-full`}>
|
<div css={tw`flex items-center px-2 py-1 bg-neutral-500 text-sm rounded-full`}>
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useContext, useEffect } from 'react';
|
||||||
import Modal from '@/components/elements/Modal';
|
|
||||||
import { Schedule, Task } from '@/api/server/schedules/getServerSchedules';
|
import { Schedule, Task } from '@/api/server/schedules/getServerSchedules';
|
||||||
import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
import { Field as FormikField, Form, Formik, FormikHelpers, useField } from 'formik';
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import createOrUpdateScheduleTask from '@/api/server/schedules/createOrUpdateScheduleTask';
|
import createOrUpdateScheduleTask from '@/api/server/schedules/createOrUpdateScheduleTask';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import { number, object, string } from 'yup';
|
import { boolean, number, object, string } from 'yup';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
|
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
@ -15,105 +14,66 @@ import Label from '@/components/elements/Label';
|
||||||
import { Textarea } from '@/components/elements/Input';
|
import { Textarea } from '@/components/elements/Input';
|
||||||
import Button from '@/components/elements/Button';
|
import Button from '@/components/elements/Button';
|
||||||
import Select from '@/components/elements/Select';
|
import Select from '@/components/elements/Select';
|
||||||
|
import ModalContext from '@/context/ModalContext';
|
||||||
|
import asModal from '@/hoc/asModal';
|
||||||
|
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
schedule: Schedule;
|
schedule: Schedule;
|
||||||
// If a task is provided we can assume we're editing it. If not provided,
|
// If a task is provided we can assume we're editing it. If not provided,
|
||||||
// we are creating a new one.
|
// we are creating a new one.
|
||||||
task?: Task;
|
task?: Task;
|
||||||
onDismissed: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
action: string;
|
action: string;
|
||||||
payload: string;
|
payload: string;
|
||||||
timeOffset: string;
|
timeOffset: string;
|
||||||
|
continueOnFailure: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
const schema = object().shape({
|
||||||
const { values: { action }, initialValues, setFieldValue, setFieldTouched, isSubmitting } = useFormikContext<Values>();
|
action: string().required().oneOf([ 'command', 'power', 'backup' ]),
|
||||||
|
payload: string().when('action', {
|
||||||
|
is: v => v !== 'backup',
|
||||||
|
then: string().required('A task payload must be provided.'),
|
||||||
|
otherwise: string(),
|
||||||
|
}),
|
||||||
|
continueOnFailure: boolean(),
|
||||||
|
timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.')
|
||||||
|
.required('A time offset value must be provided.')
|
||||||
|
.min(0, 'The time offset must be at least 0 seconds.')
|
||||||
|
.max(900, 'The time offset must be less than 900 seconds.'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ActionListener = () => {
|
||||||
|
const [ { value }, { initialValue: initialAction } ] = useField<string>('action');
|
||||||
|
const [ , { initialValue: initialPayload }, { setValue, setTouched } ] = useField<string>('payload');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (action !== initialValues.action) {
|
if (value !== initialAction) {
|
||||||
setFieldValue('payload', action === 'power' ? 'start' : '');
|
setValue(value === 'power' ? 'start' : '');
|
||||||
setFieldTouched('payload', false);
|
setTouched(false);
|
||||||
} else {
|
} else {
|
||||||
setFieldValue('payload', initialValues.payload);
|
setValue(initialPayload || '');
|
||||||
setFieldTouched('payload', false);
|
setTouched(false);
|
||||||
}
|
}
|
||||||
}, [ action ]);
|
}, [ value ]);
|
||||||
|
|
||||||
return (
|
return null;
|
||||||
<Form css={tw`m-0`}>
|
|
||||||
<h2 css={tw`text-2xl mb-6`}>{isEditingTask ? 'Edit Task' : 'Create Task'}</h2>
|
|
||||||
<div css={tw`flex`}>
|
|
||||||
<div css={tw`mr-2 w-1/3`}>
|
|
||||||
<Label>Action</Label>
|
|
||||||
<FormikFieldWrapper name={'action'}>
|
|
||||||
<FormikField as={Select} name={'action'}>
|
|
||||||
<option value={'command'}>Send command</option>
|
|
||||||
<option value={'power'}>Send power action</option>
|
|
||||||
<option value={'backup'}>Create backup</option>
|
|
||||||
</FormikField>
|
|
||||||
</FormikFieldWrapper>
|
|
||||||
</div>
|
|
||||||
<div css={tw`flex-1 ml-6`}>
|
|
||||||
<Field
|
|
||||||
name={'timeOffset'}
|
|
||||||
label={'Time offset (in seconds)'}
|
|
||||||
description={'The amount of time to wait after the previous task executes before running this one. If this is the first task on a schedule this will not be applied.'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div css={tw`mt-6`}>
|
|
||||||
{action === 'command' ?
|
|
||||||
<div>
|
|
||||||
<Label>Payload</Label>
|
|
||||||
<FormikFieldWrapper name={'payload'}>
|
|
||||||
<FormikField as={Textarea} name={'payload'} rows={6} />
|
|
||||||
</FormikFieldWrapper>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
action === 'power' ?
|
|
||||||
<div>
|
|
||||||
<Label>Payload</Label>
|
|
||||||
<FormikFieldWrapper name={'payload'}>
|
|
||||||
<FormikField as={Select} name={'payload'}>
|
|
||||||
<option value={'start'}>Start the server</option>
|
|
||||||
<option value={'restart'}>Restart the server</option>
|
|
||||||
<option value={'stop'}>Stop the server</option>
|
|
||||||
<option value={'kill'}>Terminate the server</option>
|
|
||||||
</FormikField>
|
|
||||||
</FormikFieldWrapper>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
<div>
|
|
||||||
<Label>Ignored Files</Label>
|
|
||||||
<FormikFieldWrapper
|
|
||||||
name={'payload'}
|
|
||||||
description={'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .pteroignore file will be used. If you have reached your backup limit, the oldest backup will be rotated.'}
|
|
||||||
>
|
|
||||||
<FormikField as={Textarea} name={'payload'} rows={6} />
|
|
||||||
</FormikFieldWrapper>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div css={tw`flex justify-end mt-6`}>
|
|
||||||
<Button type={'submit'} disabled={isSubmitting}>
|
|
||||||
{isEditingTask ? 'Save Changes' : 'Create Task'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ({ task, schedule, onDismissed }: Props) => {
|
const TaskDetailsModal = ({ schedule, task }: Props) => {
|
||||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
const { dismiss } = useContext(ModalContext);
|
||||||
const { clearFlashes, addError } = useFlash();
|
const { clearFlashes, addError } = useFlash();
|
||||||
|
|
||||||
|
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||||
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
|
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearFlashes('schedule:task');
|
return () => {
|
||||||
|
clearFlashes('schedule:task');
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||||
|
@ -126,7 +86,7 @@ export default ({ task, schedule, onDismissed }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
appendSchedule({ ...schedule, tasks });
|
appendSchedule({ ...schedule, tasks });
|
||||||
onDismissed();
|
dismiss();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -138,35 +98,87 @@ export default ({ task, schedule, onDismissed }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
onSubmit={submit}
|
onSubmit={submit}
|
||||||
|
validationSchema={schema}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
action: task?.action || 'command',
|
action: task?.action || 'command',
|
||||||
payload: task?.payload || '',
|
payload: task?.payload || '',
|
||||||
timeOffset: task?.timeOffset.toString() || '0',
|
timeOffset: task?.timeOffset.toString() || '0',
|
||||||
|
continueOnFailure: task?.continueOnFailure || false,
|
||||||
}}
|
}}
|
||||||
validationSchema={object().shape({
|
|
||||||
action: string().required().oneOf([ 'command', 'power', 'backup' ]),
|
|
||||||
payload: string().when('action', {
|
|
||||||
is: v => v !== 'backup',
|
|
||||||
then: string().required('A task payload must be provided.'),
|
|
||||||
otherwise: string(),
|
|
||||||
}),
|
|
||||||
timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.')
|
|
||||||
.required('A time offset value must be provided.')
|
|
||||||
.min(0, 'The time offset must be at least 0 seconds.')
|
|
||||||
.max(900, 'The time offset must be less than 900 seconds.'),
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{({ isSubmitting }) => (
|
{({ isSubmitting, values }) => (
|
||||||
<Modal
|
<Form css={tw`m-0`}>
|
||||||
visible
|
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`}/>
|
||||||
appear
|
<h2 css={tw`text-2xl mb-6`}>{task ? 'Edit Task' : 'Create Task'}</h2>
|
||||||
onDismissed={() => onDismissed()}
|
<div css={tw`flex`}>
|
||||||
showSpinnerOverlay={isSubmitting}
|
<div css={tw`mr-2 w-1/3`}>
|
||||||
>
|
<Label>Action</Label>
|
||||||
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`} />
|
<ActionListener/>
|
||||||
<TaskDetailsForm isEditingTask={typeof task !== 'undefined'} />
|
<FormikFieldWrapper name={'action'}>
|
||||||
</Modal>
|
<FormikField as={Select} name={'action'}>
|
||||||
|
<option value={'command'}>Send command</option>
|
||||||
|
<option value={'power'}>Send power action</option>
|
||||||
|
<option value={'backup'}>Create backup</option>
|
||||||
|
</FormikField>
|
||||||
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
|
<div css={tw`flex-1 ml-6`}>
|
||||||
|
<Field
|
||||||
|
name={'timeOffset'}
|
||||||
|
label={'Time offset (in seconds)'}
|
||||||
|
description={'The amount of time to wait after the previous task executes before running this one. If this is the first task on a schedule this will not be applied.'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div css={tw`mt-6`}>
|
||||||
|
{values.action === 'command' ?
|
||||||
|
<div>
|
||||||
|
<Label>Payload</Label>
|
||||||
|
<FormikFieldWrapper name={'payload'}>
|
||||||
|
<FormikField as={Textarea} name={'payload'} rows={6}/>
|
||||||
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
values.action === 'power' ?
|
||||||
|
<div>
|
||||||
|
<Label>Payload</Label>
|
||||||
|
<FormikFieldWrapper name={'payload'}>
|
||||||
|
<FormikField as={Select} name={'payload'}>
|
||||||
|
<option value={'start'}>Start the server</option>
|
||||||
|
<option value={'restart'}>Restart the server</option>
|
||||||
|
<option value={'stop'}>Stop the server</option>
|
||||||
|
<option value={'kill'}>Terminate the server</option>
|
||||||
|
</FormikField>
|
||||||
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<Label>Ignored Files</Label>
|
||||||
|
<FormikFieldWrapper
|
||||||
|
name={'payload'}
|
||||||
|
description={'Optional. Include the files and folders to be excluded in this backup. By default, the contents of your .pteroignore file will be used. If you have reached your backup limit, the oldest backup will be rotated.'}
|
||||||
|
>
|
||||||
|
<FormikField as={Textarea} name={'payload'} rows={6}/>
|
||||||
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
|
||||||
|
<FormikSwitch
|
||||||
|
name={'continueOnFailure'}
|
||||||
|
description={'Future tasks will be run when this task fails.'}
|
||||||
|
label={'Continue on Failure'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div css={tw`flex justify-end mt-6`}>
|
||||||
|
<Button type={'submit'} disabled={isSubmitting}>
|
||||||
|
{task ? 'Save Changes' : 'Create Task'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default asModal<Props>()(TaskDetailsModal);
|
||||||
|
|
Loading…
Reference in New Issue