Apply new eslint rules; default to prettier for styling

This commit is contained in:
DaneEveritt 2022-06-26 15:13:52 -04:00
parent f22cce8881
commit dc84af9937
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
218 changed files with 3876 additions and 3564 deletions

View file

@ -14,9 +14,9 @@ interface Props {
}
export default ({ scheduleId, onDeleted }: Props) => {
const [ visible, setVisible ] = useState(false);
const [ isLoading, setIsLoading ] = useState(false);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const [visible, setVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const onDelete = () => {
@ -27,7 +27,7 @@ export default ({ scheduleId, onDeleted }: Props) => {
setIsLoading(false);
onDeleted();
})
.catch(error => {
.catch((error) => {
console.error(error);
addError({ key: 'schedules', message: httpErrorToHuman(error) });

View file

@ -34,9 +34,9 @@ const EditScheduleModal = ({ schedule }: Props) => {
const { addError, clearFlashes } = useFlash();
const { dismiss } = useContext(ModalContext);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
const [ showCheatsheet, setShowCheetsheet ] = useState(false);
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
const [showCheatsheet, setShowCheetsheet] = useState(false);
useEffect(() => {
return () => {
@ -59,12 +59,12 @@ const EditScheduleModal = ({ schedule }: Props) => {
onlyWhenOnline: values.onlyWhenOnline,
isActive: values.enabled,
})
.then(schedule => {
.then((schedule) => {
setSubmitting(false);
appendSchedule(schedule);
dismiss();
})
.catch(error => {
.catch((error) => {
console.error(error);
setSubmitting(false);
@ -75,32 +75,34 @@ const EditScheduleModal = ({ schedule }: Props) => {
return (
<Formik
onSubmit={submit}
initialValues={{
name: schedule?.name || '',
minute: schedule?.cron.minute || '*/5',
hour: schedule?.cron.hour || '*',
dayOfMonth: schedule?.cron.dayOfMonth || '*',
month: schedule?.cron.month || '*',
dayOfWeek: schedule?.cron.dayOfWeek || '*',
enabled: schedule?.isActive ?? true,
onlyWhenOnline: schedule?.onlyWhenOnline ?? true,
} as Values}
initialValues={
{
name: schedule?.name || '',
minute: schedule?.cron.minute || '*/5',
hour: schedule?.cron.hour || '*',
dayOfMonth: schedule?.cron.dayOfMonth || '*',
month: schedule?.cron.month || '*',
dayOfWeek: schedule?.cron.dayOfWeek || '*',
enabled: schedule?.isActive ?? true,
onlyWhenOnline: schedule?.onlyWhenOnline ?? true,
} as Values
}
>
{({ isSubmitting }) => (
<Form>
<h3 css={tw`text-2xl mb-6`}>{schedule ? 'Edit schedule' : 'Create new schedule'}</h3>
<FlashMessageRender byKey={'schedule:edit'} css={tw`mb-6`}/>
<FlashMessageRender byKey={'schedule:edit'} css={tw`mb-6`} />
<Field
name={'name'}
label={'Schedule name'}
description={'A human readable identifier for this schedule.'}
/>
<div css={tw`grid grid-cols-2 sm:grid-cols-5 gap-4 mt-6`}>
<Field name={'minute'} label={'Minute'}/>
<Field name={'hour'} label={'Hour'}/>
<Field name={'dayOfMonth'} label={'Day of month'}/>
<Field name={'month'} label={'Month'}/>
<Field name={'dayOfWeek'} label={'Day of week'}/>
<Field name={'minute'} label={'Minute'} />
<Field name={'hour'} label={'Hour'} />
<Field name={'dayOfMonth'} label={'Day of month'} />
<Field name={'month'} label={'Month'} />
<Field name={'dayOfWeek'} label={'Day of week'} />
</div>
<p css={tw`text-neutral-400 text-xs mt-2`}>
The schedule system supports the use of Cronjob syntax when defining when tasks should begin
@ -112,13 +114,13 @@ const EditScheduleModal = ({ schedule }: Props) => {
description={'Show the cron cheatsheet for some examples.'}
label={'Show Cheatsheet'}
defaultChecked={showCheatsheet}
onChange={() => setShowCheetsheet(s => !s)}
onChange={() => setShowCheetsheet((s) => !s)}
/>
{showCheatsheet &&
{showCheatsheet && (
<div css={tw`block md:flex w-full`}>
<ScheduleCheatsheetCards/>
<ScheduleCheatsheetCards />
</div>
}
)}
</div>
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
<FormikSwitch

View file

@ -8,11 +8,11 @@ interface Props {
}
export default ({ schedule }: Props) => {
const [ visible, setVisible ] = useState(false);
const [visible, setVisible] = useState(false);
return (
<>
<TaskDetailsModal schedule={schedule} visible={visible} onModalDismissed={() => setVisible(false)}/>
<TaskDetailsModal schedule={schedule} visible={visible} onModalDismissed={() => setVisible(false)} />
<Button onClick={() => setVisible(true)} className={'flex-1'}>
New Task
</Button>

View file

@ -7,11 +7,11 @@ import useFlash from '@/plugins/useFlash';
import { Schedule } from '@/api/server/schedules/getServerSchedules';
const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => {
const [ loading, setLoading ] = useState(false);
const [loading, setLoading] = useState(false);
const { clearFlashes, clearAndAddHttpError } = useFlash();
const id = ServerContext.useStoreState(state => state.server.data!.id);
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
const id = ServerContext.useStoreState((state) => state.server.data!.id);
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
const onTriggerExecute = useCallback(() => {
clearFlashes('schedule');
@ -21,7 +21,7 @@ const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => {
setLoading(false);
appendSchedule({ ...schedule, isProcessing: true });
})
.catch(error => {
.catch((error) => {
console.error(error);
clearAndAddHttpError({ error, key: 'schedules' });
})
@ -30,7 +30,7 @@ const RunScheduleButton = ({ schedule }: { schedule: Schedule }) => {
return (
<>
<SpinnerOverlay visible={loading} size={'large'}/>
<SpinnerOverlay visible={loading} size={'large'} />
<Button
variant={Button.Variants.Secondary}
className={'flex-1 sm:flex-none'}

View file

@ -18,19 +18,19 @@ export default () => {
const match = useRouteMatch();
const history = useHistory();
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const { clearFlashes, addError } = useFlash();
const [ loading, setLoading ] = useState(true);
const [ visible, setVisible ] = useState(false);
const [loading, setLoading] = useState(true);
const [visible, setVisible] = useState(false);
const schedules = ServerContext.useStoreState(state => state.schedules.data);
const setSchedules = ServerContext.useStoreActions(actions => actions.schedules.setSchedules);
const schedules = ServerContext.useStoreState((state) => state.schedules.data);
const setSchedules = ServerContext.useStoreActions((actions) => actions.schedules.setSchedules);
useEffect(() => {
clearFlashes('schedules');
getServerSchedules(uuid)
.then(schedules => setSchedules(schedules))
.catch(error => {
.then((schedules) => setSchedules(schedules))
.catch((error) => {
addError({ message: httpErrorToHuman(error), key: 'schedules' });
console.error(error);
})
@ -39,42 +39,41 @@ export default () => {
return (
<ServerContentBlock title={'Schedules'}>
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
{(!schedules.length && loading) ?
<Spinner size={'large'} centered/>
:
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`} />
{!schedules.length && loading ? (
<Spinner size={'large'} centered />
) : (
<>
{
schedules.length === 0 ?
<p css={tw`text-sm text-center text-neutral-300`}>
There are no schedules configured for this server.
</p>
:
schedules.map(schedule => (
<GreyRowBox
as={'a'}
key={schedule.id}
href={`${match.url}/${schedule.id}`}
css={tw`cursor-pointer mb-2 flex-wrap`}
onClick={(e: any) => {
e.preventDefault();
history.push(`${match.url}/${schedule.id}`);
}}
>
<ScheduleRow schedule={schedule}/>
</GreyRowBox>
))
}
{schedules.length === 0 ? (
<p css={tw`text-sm text-center text-neutral-300`}>
There are no schedules configured for this server.
</p>
) : (
schedules.map((schedule) => (
<GreyRowBox
as={'a'}
key={schedule.id}
href={`${match.url}/${schedule.id}`}
css={tw`cursor-pointer mb-2 flex-wrap`}
onClick={(e: any) => {
e.preventDefault();
history.push(`${match.url}/${schedule.id}`);
}}
>
<ScheduleRow schedule={schedule} />
</GreyRowBox>
))
)}
<Can action={'schedule.create'}>
<div css={tw`mt-8 flex justify-end`}>
<EditScheduleModal visible={visible} onModalDismissed={() => setVisible(false)}/>
<EditScheduleModal visible={visible} onModalDismissed={() => setVisible(false)} />
<Button type={'button'} onClick={() => setVisible(true)}>
Create schedule
</Button>
</div>
</Can>
</>
}
)}
</ServerContentBlock>
);
};

View file

@ -44,15 +44,18 @@ export default () => {
const history = useHistory();
const { id: scheduleId } = useParams<Params>();
const id = ServerContext.useStoreState(state => state.server.data!.id);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const id = ServerContext.useStoreState((state) => state.server.data!.id);
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const { clearFlashes, clearAndAddHttpError } = useFlash();
const [ isLoading, setIsLoading ] = useState(true);
const [ showEditModal, setShowEditModal ] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [showEditModal, setShowEditModal] = useState(false);
const schedule = ServerContext.useStoreState(st => st.schedules.data.find(s => s.id === Number(scheduleId)), isEqual);
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
const schedule = ServerContext.useStoreState(
(st) => st.schedules.data.find((s) => s.id === Number(scheduleId)),
isEqual
);
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
useEffect(() => {
if (schedule?.id === Number(scheduleId)) {
@ -62,56 +65,58 @@ export default () => {
clearFlashes('schedules');
getServerSchedule(uuid, Number(scheduleId))
.then(schedule => appendSchedule(schedule))
.catch(error => {
.then((schedule) => appendSchedule(schedule))
.catch((error) => {
console.error(error);
clearAndAddHttpError({ error, key: 'schedules' });
})
.then(() => setIsLoading(false));
}, [ scheduleId ]);
}, [scheduleId]);
const toggleEditModal = useCallback(() => {
setShowEditModal(s => !s);
setShowEditModal((s) => !s);
}, []);
return (
<PageContentBlock title={'Schedules'}>
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
{!schedule || isLoading ?
<Spinner size={'large'} centered/>
:
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`} />
{!schedule || isLoading ? (
<Spinner size={'large'} centered />
) : (
<>
<ScheduleCronRow cron={schedule.cron} css={tw`sm:hidden bg-neutral-700 rounded mb-4 p-3`}/>
<ScheduleCronRow cron={schedule.cron} css={tw`sm:hidden bg-neutral-700 rounded mb-4 p-3`} />
<div css={tw`rounded shadow`}>
<div css={tw`sm:flex items-center bg-neutral-900 p-3 sm:p-6 border-b-4 border-neutral-600 rounded-t`}>
<div
css={tw`sm:flex items-center bg-neutral-900 p-3 sm:p-6 border-b-4 border-neutral-600 rounded-t`}
>
<div css={tw`flex-1`}>
<h3 css={tw`flex items-center text-neutral-100 text-2xl`}>
{schedule.name}
{schedule.isProcessing ?
{schedule.isProcessing ? (
<span
css={tw`flex items-center rounded-full px-2 py-px text-xs ml-4 uppercase bg-neutral-600 text-white`}
>
<Spinner css={tw`w-3! h-3! mr-2`}/>
<Spinner css={tw`w-3! h-3! mr-2`} />
Processing
</span>
:
<ActivePill active={schedule.isActive}/>
}
) : (
<ActivePill active={schedule.isActive} />
)}
</h3>
<p css={tw`mt-1 text-sm text-neutral-200`}>
Last run at:&nbsp;
{schedule.lastRunAt ?
format(schedule.lastRunAt, 'MMM do \'at\' h:mma')
:
{schedule.lastRunAt ? (
format(schedule.lastRunAt, "MMM do 'at' h:mma")
) : (
<span css={tw`text-neutral-300`}>n/a</span>
}
)}
<span css={tw`ml-4 pl-4 border-l-4 border-neutral-600 py-px`}>
Next run at:&nbsp;
{schedule.nextRunAt ?
format(schedule.nextRunAt, 'MMM do \'at\' h:mma')
:
{schedule.nextRunAt ? (
format(schedule.nextRunAt, "MMM do 'at' h:mma")
) : (
<span css={tw`text-neutral-300`}>n/a</span>
}
)}
</span>
</p>
</div>
@ -120,28 +125,34 @@ export default () => {
<Button.Text className={'flex-1 mr-4'} onClick={toggleEditModal}>
Edit
</Button.Text>
<NewTaskButton schedule={schedule}/>
<NewTaskButton schedule={schedule} />
</Can>
</div>
</div>
<div css={tw`hidden sm:grid grid-cols-5 md:grid-cols-5 gap-4 mb-4 mt-4`}>
<CronBox title={'Minute'} value={schedule.cron.minute}/>
<CronBox title={'Hour'} value={schedule.cron.hour}/>
<CronBox title={'Day (Month)'} value={schedule.cron.dayOfMonth}/>
<CronBox title={'Month'} value={schedule.cron.month}/>
<CronBox title={'Day (Week)'} value={schedule.cron.dayOfWeek}/>
<CronBox title={'Minute'} value={schedule.cron.minute} />
<CronBox title={'Hour'} value={schedule.cron.hour} />
<CronBox title={'Day (Month)'} value={schedule.cron.dayOfMonth} />
<CronBox title={'Month'} value={schedule.cron.month} />
<CronBox title={'Day (Week)'} value={schedule.cron.dayOfWeek} />
</div>
<div css={tw`bg-neutral-700 rounded-b`}>
{schedule.tasks.length > 0 ?
schedule.tasks.sort((a, b) => a.sequenceId === b.sequenceId ? 0 : (a.sequenceId > b.sequenceId ? 1 : -1)).map(task => (
<ScheduleTaskRow key={`${schedule.id}_${task.id}`} task={task} schedule={schedule}/>
))
:
null
}
{schedule.tasks.length > 0
? schedule.tasks
.sort((a, b) =>
a.sequenceId === b.sequenceId ? 0 : a.sequenceId > b.sequenceId ? 1 : -1
)
.map((task) => (
<ScheduleTaskRow
key={`${schedule.id}_${task.id}`}
task={task}
schedule={schedule}
/>
))
: null}
</div>
</div>
<EditScheduleModal visible={showEditModal} schedule={schedule} onModalDismissed={toggleEditModal}/>
<EditScheduleModal visible={showEditModal} schedule={schedule} onModalDismissed={toggleEditModal} />
<div css={tw`mt-6 flex sm:justify-end`}>
<Can action={'schedule.delete'}>
<DeleteScheduleButton
@ -149,14 +160,14 @@ export default () => {
onDeleted={() => history.push(`/server/${id}/schedules`)}
/>
</Can>
{schedule.tasks.length > 0 &&
<Can action={'schedule.update'}>
<RunScheduleButton schedule={schedule}/>
</Can>
}
{schedule.tasks.length > 0 && (
<Can action={'schedule.update'}>
<RunScheduleButton schedule={schedule} />
</Can>
)}
</div>
</>
}
)}
</PageContentBlock>
);
};

View file

@ -9,13 +9,12 @@ import ScheduleCronRow from '@/components/server/schedules/ScheduleCronRow';
export default ({ schedule }: { schedule: Schedule }) => (
<>
<div css={tw`hidden md:block`}>
<FontAwesomeIcon icon={faCalendarAlt} fixedWidth/>
<FontAwesomeIcon icon={faCalendarAlt} fixedWidth />
</div>
<div css={tw`flex-1 md:ml-4`}>
<p>{schedule.name}</p>
<p css={tw`text-xs text-neutral-400`}>
Last run
at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM do \'at\' h:mma') : 'never'}
Last run at: {schedule.lastRunAt ? format(schedule.lastRunAt, "MMM do 'at' h:mma") : 'never'}
</p>
</div>
<div>
@ -28,7 +27,7 @@ export default ({ schedule }: { schedule: Schedule }) => (
{schedule.isActive ? 'Active' : 'Inactive'}
</p>
</div>
<ScheduleCronRow cron={schedule.cron} css={tw`mx-auto sm:mx-8 w-full sm:w-auto mt-4 sm:mt-0`}/>
<ScheduleCronRow cron={schedule.cron} css={tw`mx-auto sm:mx-8 w-full sm:w-auto mt-4 sm:mt-0`} />
<div>
<p
css={[
@ -36,11 +35,7 @@ export default ({ schedule }: { schedule: Schedule }) => (
schedule.isActive && !schedule.isProcessing ? tw`bg-green-600` : tw`bg-neutral-400`,
]}
>
{schedule.isProcessing ?
'Processing'
:
schedule.isActive ? 'Active' : 'Inactive'
}
{schedule.isProcessing ? 'Processing' : schedule.isActive ? 'Active' : 'Inactive'}
</p>
</div>
</>

View file

@ -26,47 +26,49 @@ interface Props {
task: Task;
}
const getActionDetails = (action: string): [ string, any ] => {
const getActionDetails = (action: string): [string, any] => {
switch (action) {
case 'command':
return [ 'Send Command', faCode ];
return ['Send Command', faCode];
case 'power':
return [ 'Send Power Action', faToggleOn ];
return ['Send Power Action', faToggleOn];
case 'backup':
return [ 'Create Backup', faFileArchive ];
return ['Create Backup', faFileArchive];
default:
return [ 'Unknown Action', faCode ];
return ['Unknown Action', faCode];
}
};
export default ({ schedule, task }: Props) => {
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
const { clearFlashes, addError } = useFlash();
const [ visible, setVisible ] = useState(false);
const [ isLoading, setIsLoading ] = useState(false);
const [ isEditing, setIsEditing ] = useState(false);
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
const [visible, setVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const appendSchedule = ServerContext.useStoreActions((actions) => actions.schedules.appendSchedule);
const onConfirmDeletion = () => {
setIsLoading(true);
clearFlashes('schedules');
deleteScheduleTask(uuid, schedule.id, task.id)
.then(() => appendSchedule({
...schedule,
tasks: schedule.tasks.filter(t => t.id !== task.id),
}))
.catch(error => {
.then(() =>
appendSchedule({
...schedule,
tasks: schedule.tasks.filter((t) => t.id !== task.id),
})
)
.catch((error) => {
console.error(error);
setIsLoading(false);
addError({ message: httpErrorToHuman(error), key: 'schedules' });
});
};
const [ title, icon ] = getActionDetails(task.action);
const [title, icon] = getActionDetails(task.action);
return (
<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'} />
<TaskDetailsModal
schedule={schedule}
task={task}
@ -82,38 +84,39 @@ export default ({ schedule, task }: Props) => {
>
Are you sure you want to delete this task? This action cannot be undone.
</ConfirmationModal>
<FontAwesomeIcon icon={icon} css={tw`text-lg text-white hidden md:block`}/>
<FontAwesomeIcon icon={icon} css={tw`text-lg text-white hidden md:block`} />
<div css={tw`flex-none sm:flex-1 w-full sm:w-auto overflow-x-auto`}>
<p css={tw`md:ml-6 text-neutral-200 uppercase text-sm`}>
{title}
</p>
{task.payload &&
<div css={tw`md:ml-6 mt-2`}>
{task.action === 'backup' &&
<p css={tw`text-xs uppercase text-neutral-400 mb-1`}>Ignoring files & folders:</p>}
<div css={tw`font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto inline-block whitespace-pre-wrap break-all`}>
{task.payload}
<p css={tw`md:ml-6 text-neutral-200 uppercase text-sm`}>{title}</p>
{task.payload && (
<div css={tw`md:ml-6 mt-2`}>
{task.action === 'backup' && (
<p css={tw`text-xs uppercase text-neutral-400 mb-1`}>Ignoring files & folders:</p>
)}
<div
css={tw`font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto inline-block whitespace-pre-wrap break-all`}
>
{task.payload}
</div>
</div>
</div>
}
)}
</div>
<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
{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>
</div>
}
{task.sequenceId > 1 && task.timeOffset > 0 &&
<div css={tw`mr-6`}>
<div css={tw`flex items-center px-2 py-1 bg-neutral-500 text-sm rounded-full`}>
<Icon icon={faClock} css={tw`w-3 h-3 mr-2`}/>
{task.timeOffset}s later
)}
{task.sequenceId > 1 && task.timeOffset > 0 && (
<div css={tw`mr-6`}>
<div css={tw`flex items-center px-2 py-1 bg-neutral-500 text-sm rounded-full`}>
<Icon icon={faClock} css={tw`w-3 h-3 mr-2`} />
{task.timeOffset}s later
</div>
</div>
</div>
}
)}
<Can action={'schedule.update'}>
<button
type={'button'}
@ -121,7 +124,7 @@ export default ({ schedule, task }: Props) => {
css={tw`block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mr-4 ml-auto sm:ml-0`}
onClick={() => setIsEditing(true)}
>
<FontAwesomeIcon icon={faPencilAlt}/>
<FontAwesomeIcon icon={faPencilAlt} />
</button>
</Can>
<Can action={'schedule.update'}>
@ -131,7 +134,7 @@ export default ({ schedule, task }: Props) => {
css={tw`block text-sm p-2 text-neutral-500 hover:text-red-600 transition-colors duration-150`}
onClick={() => setVisible(true)}
>
<FontAwesomeIcon icon={faTrashAlt}/>
<FontAwesomeIcon icon={faTrashAlt} />
</button>
</Can>
</div>

View file

@ -33,22 +33,23 @@ interface Values {
}
const schema = object().shape({
action: string().required().oneOf([ 'command', 'power', 'backup' ]),
action: string().required().oneOf(['command', 'power', 'backup']),
payload: string().when('action', {
is: v => v !== 'backup',
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.')
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');
const [{ value }, { initialValue: initialAction }] = useField<string>('action');
const [, { initialValue: initialPayload }, { setValue, setTouched }] = useField<string>('payload');
useEffect(() => {
if (value !== initialAction) {
@ -58,7 +59,7 @@ const ActionListener = () => {
setValue(initialPayload || '');
setTouched(false);
}
}, [ value ]);
}, [value]);
return null;
};
@ -67,9 +68,9 @@ 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);
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 () => {
@ -81,19 +82,22 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
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' });
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 ];
.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 => {
.catch((error) => {
console.error(error);
setSubmitting(false);
addError({ message: httpErrorToHuman(error), key: 'schedule:task' });
@ -114,12 +118,12 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
>
{({ isSubmitting, values }) => (
<Form css={tw`m-0`}>
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`}/>
<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/>
<ActionListener />
<FormikFieldWrapper name={'action'}>
<FormikField as={Select} name={'action'}>
<option value={'command'}>Send command</option>
@ -132,42 +136,45 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
<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.'}
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' ?
{values.action === 'command' ? (
<div>
<Label>Payload</Label>
<FormikFieldWrapper name={'payload'}>
<FormikField as={Textarea} name={'payload'} rows={6}/>
<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>
}
) : 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