forked from pterodactyl/panel
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTaskDetailsModal.tsx
More file actions
197 lines (184 loc) · 8.76 KB
/
TaskDetailsModal.tsx
File metadata and controls
197 lines (184 loc) · 8.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import React, { useContext, useEffect } from 'react';
import { Schedule, Task } from '@/api/server/schedules/getServerSchedules';
import { Field as FormikField, Form, Formik, FormikHelpers, useField } from 'formik';
import { ServerContext } from '@/state/server';
import createOrUpdateScheduleTask from '@/api/server/schedules/createOrUpdateScheduleTask';
import { httpErrorToHuman } from '@/api/http';
import Field from '@/components/elements/Field';
import FlashMessageRender from '@/components/FlashMessageRender';
import { boolean, number, object, string } from 'yup';
import useFlash from '@/plugins/useFlash';
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
import tw from 'twin.macro';
import Label from '@/components/elements/Label';
import { Textarea } from '@/components/elements/Input';
import { Button } from '@/components/elements/button/index';
import Select from '@/components/elements/Select';
import ModalContext from '@/context/ModalContext';
import asModal from '@/hoc/asModal';
import FormikSwitch from '@/components/elements/FormikSwitch';
interface Props {
schedule: Schedule;
// If a task is provided we can assume we're editing it. If not provided,
// we are creating a new one.
task?: Task;
}
interface Values {
action: string;
payload: string;
timeOffset: string;
continueOnFailure: boolean;
}
const schema = 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(),
}),
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(() => {
if (value !== initialAction) {
setValue(value === 'power' ? 'start' : '');
setTouched(false);
} else {
setValue(initialPayload || '');
setTouched(false);
}
}, [value]);
return null;
};
const TaskDetailsModal = ({ schedule, task }: Props) => {
const { dismiss } = useContext(ModalContext);
const { clearFlashes, addError } = useFlash();
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups);
useEffect(() => {
return () => {
clearFlashes('schedule:task');
};
}, []);
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes('schedule:task');
if (backupLimit === 0 && values.action === 'backup') {
setSubmitting(false);
addError({
message: "A backup task cannot be created when the server's backup limit is set to 0.",
key: 'schedule:task',
});
} else {
createOrUpdateScheduleTask(uuid, schedule.id, task?.id, values)
.then((task) => {
let tasks = schedule.tasks.map((t) => (t.id === task.id ? task : t));
if (!schedule.tasks.find((t) => t.id === task.id)) {
tasks = [...tasks, task];
}
appendSchedule({ ...schedule, tasks });
dismiss();
})
.catch((error) => {
console.error(error);
setSubmitting(false);
addError({ message: httpErrorToHuman(error), key: 'schedule:task' });
});
}
};
return (
<Formik
onSubmit={submit}
validationSchema={schema}
initialValues={{
action: task?.action || 'command',
payload: task?.payload || '',
timeOffset: task?.timeOffset.toString() || '0',
continueOnFailure: task?.continueOnFailure || false,
}}
>
{({ isSubmitting, values }) => (
<Form css={tw`m-0`}>
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`} />
<h2 css={tw`text-2xl mb-6`}>{task ? 'Edit Task' : 'Create Task'}</h2>
<div css={tw`flex`}>
<div css={tw`mr-2 w-1/3`}>
<Label>Action</Label>
<ActionListener />
<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`}>
{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>
);
};
export default asModal<Props>()(TaskDetailsModal);