Apply new eslint rules; default to prettier for styling
This commit is contained in:
parent
f22cce8881
commit
dc84af9937
218 changed files with 3876 additions and 3564 deletions
|
@ -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) });
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
{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:
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue