Update schedule page
This commit is contained in:
parent
f3586056f4
commit
a288374027
12 changed files with 180 additions and 168 deletions
|
@ -18,7 +18,7 @@ const ConfirmationModal = ({ title, appear, children, visible, buttonText, onCon
|
|||
showSpinnerOverlay={showSpinnerOverlay}
|
||||
onDismissed={() => onDismissed()}
|
||||
>
|
||||
<h3 css={tw`mb-6`}>{title}</h3>
|
||||
<h2 css={tw`text-2xl mb-6`}>{title}</h2>
|
||||
<p css={tw`text-sm`}>{children}</p>
|
||||
<div css={tw`flex items-center justify-end mt-8`}>
|
||||
<Button isSecondary onClick={() => onDismissed()}>
|
||||
|
|
36
resources/scripts/components/elements/Select.tsx
Normal file
36
resources/scripts/components/elements/Select.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import styled, { css } from 'styled-components/macro';
|
||||
import tw from 'twin.macro';
|
||||
|
||||
interface Props {
|
||||
hideDropdownArrow?: boolean;
|
||||
}
|
||||
|
||||
const Select = styled.select<Props>`
|
||||
${tw`shadow-none block p-3 pr-8 rounded border w-full text-sm transition-colors duration-150 ease-linear`};
|
||||
|
||||
&, &:hover:not(:disabled), &:focus {
|
||||
${tw`outline-none`};
|
||||
}
|
||||
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-size: 1rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: calc(100% - 0.75rem);
|
||||
background-position-y: center;
|
||||
|
||||
&::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
${props => !props.hideDropdownArrow && css`
|
||||
${tw`bg-neutral-600 border-neutral-500 text-neutral-200`};
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='%23C3D1DF' d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z'/%3e%3c/svg%3e ");
|
||||
|
||||
&:hover:not(:disabled), &:focus {
|
||||
${tw`border-neutral-400`};
|
||||
}
|
||||
`};
|
||||
`;
|
||||
|
||||
export default Select;
|
|
@ -2,6 +2,8 @@ import React, { useMemo } from 'react';
|
|||
import styled from 'styled-components/macro';
|
||||
import v4 from 'uuid/v4';
|
||||
import tw from 'twin.macro';
|
||||
import Label from '@/components/elements/Label';
|
||||
import Input from '@/components/elements/Input';
|
||||
|
||||
const ToggleContainer = styled.div`
|
||||
${tw`relative select-none w-12 leading-normal`};
|
||||
|
@ -50,7 +52,7 @@ const Switch = ({ name, label, description, defaultChecked, onChange, children }
|
|||
<div css={tw`flex items-center`}>
|
||||
<ToggleContainer css={tw`flex-none`}>
|
||||
{children
|
||||
|| <input
|
||||
|| <Input
|
||||
id={uuid}
|
||||
name={name}
|
||||
type={'checkbox'}
|
||||
|
@ -58,21 +60,20 @@ const Switch = ({ name, label, description, defaultChecked, onChange, children }
|
|||
defaultChecked={defaultChecked}
|
||||
/>
|
||||
}
|
||||
<label htmlFor={uuid}/>
|
||||
<Label htmlFor={uuid}/>
|
||||
</ToggleContainer>
|
||||
{(label || description) &&
|
||||
<div css={tw`ml-4 w-full`}>
|
||||
{label &&
|
||||
<label
|
||||
<Label
|
||||
css={[ tw`cursor-pointer`, !!description && tw`mb-0` ]}
|
||||
className={'input-dark-label'}
|
||||
htmlFor={uuid}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
</Label>
|
||||
}
|
||||
{description &&
|
||||
<p className={'input-help'}>
|
||||
<p css={tw`text-neutral-400 text-sm mt-2`}>
|
||||
{description}
|
||||
</p>
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
||||
|
||||
type Props = RequiredModalProps & {
|
||||
onConfirmed: () => void;
|
||||
}
|
||||
|
||||
export default ({ onConfirmed, ...props }: Props) => (
|
||||
<Modal {...props}>
|
||||
<h2>Confirm task deletion</h2>
|
||||
<p className={'text-sm mt-4'}>
|
||||
Are you sure you want to delete this task? This action cannot be undone.
|
||||
</p>
|
||||
<div className={'flex items-center justify-end mt-8'}>
|
||||
<button className={'btn btn-secondary btn-sm'} onClick={() => props.onDismissed()}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className={'btn btn-red btn-sm ml-4'} onClick={() => {
|
||||
props.onDismissed();
|
||||
onConfirmed();
|
||||
}}>
|
||||
Delete Task
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
|
@ -1,10 +1,12 @@
|
|||
import React, { useState } from 'react';
|
||||
import Modal from '@/components/elements/Modal';
|
||||
import deleteSchedule from '@/api/server/schedules/deleteSchedule';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { ApplicationStore } from '@/state';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||
|
||||
interface Props {
|
||||
scheduleId: number;
|
||||
|
@ -36,34 +38,19 @@ export default ({ scheduleId, onDeleted }: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
<ConfirmationModal
|
||||
title={'Delete schedule?'}
|
||||
buttonText={'Yes, delete schedule'}
|
||||
onConfirmed={onDelete}
|
||||
visible={visible}
|
||||
onDismissed={() => setVisible(false)}
|
||||
showSpinnerOverlay={isLoading}
|
||||
>
|
||||
<h3 className={'mb-6'}>Delete schedule</h3>
|
||||
<p className={'text-sm'}>
|
||||
Are you sure you want to delete this schedule? All tasks will be removed and any running processes
|
||||
will be terminated.
|
||||
</p>
|
||||
<div className={'mt-6 flex justify-end'}>
|
||||
<button
|
||||
className={'btn btn-secondary btn-sm mr-4'}
|
||||
onClick={() => setVisible(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className={'btn btn-red btn-sm'}
|
||||
onClick={() => onDelete()}
|
||||
>
|
||||
Yes, delete schedule
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
<button className={'btn btn-red btn-secondary btn-sm mr-4'} onClick={() => setVisible(true)}>
|
||||
Are you sure you want to delete this schedule? All tasks will be removed and any running processes
|
||||
will be terminated.
|
||||
</ConfirmationModal>
|
||||
<Button css={tw`mr-4`} color={'red'} isSecondary onClick={() => setVisible(true)}>
|
||||
Delete
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,6 +10,8 @@ import { httpErrorToHuman } from '@/api/http';
|
|||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import useServer from '@/plugins/useServer';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
type Props = {
|
||||
schedule?: Schedule;
|
||||
|
@ -29,43 +31,43 @@ const EditScheduleModal = ({ schedule, ...props }: Omit<Props, 'onScheduleUpdate
|
|||
|
||||
return (
|
||||
<Modal {...props} showSpinnerOverlay={isSubmitting}>
|
||||
<h3 className={'mb-6'}>{schedule ? 'Edit schedule' : 'Create new schedule'}</h3>
|
||||
<FlashMessageRender byKey={'schedule:edit'} className={'mb-6'}/>
|
||||
<h3 css={tw`mb-6`}>{schedule ? 'Edit schedule' : 'Create new schedule'}</h3>
|
||||
<FlashMessageRender byKey={'schedule:edit'} css={tw`mb-6`}/>
|
||||
<Form>
|
||||
<Field
|
||||
name={'name'}
|
||||
label={'Schedule name'}
|
||||
description={'A human readable identifer for this schedule.'}
|
||||
/>
|
||||
<div className={'flex mt-6'}>
|
||||
<div className={'flex-1 mr-4'}>
|
||||
<div css={tw`flex mt-6`}>
|
||||
<div css={tw`flex-1 mr-4`}>
|
||||
<Field name={'dayOfWeek'} label={'Day of week'}/>
|
||||
</div>
|
||||
<div className={'flex-1 mr-4'}>
|
||||
<div css={tw`flex-1 mr-4`}>
|
||||
<Field name={'dayOfMonth'} label={'Day of month'}/>
|
||||
</div>
|
||||
<div className={'flex-1 mr-4'}>
|
||||
<div css={tw`flex-1 mr-4`}>
|
||||
<Field name={'hour'} label={'Hour'}/>
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
<div css={tw`flex-1`}>
|
||||
<Field name={'minute'} label={'Minute'}/>
|
||||
</div>
|
||||
</div>
|
||||
<p className={'input-help'}>
|
||||
<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
|
||||
running. Use the fields above to specify when these tasks should begin running.
|
||||
</p>
|
||||
<div className={'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
|
||||
name={'enabled'}
|
||||
description={'If disabled, this schedule and it\'s associated tasks will not run.'}
|
||||
label={'Enabled'}
|
||||
/>
|
||||
</div>
|
||||
<div className={'mt-6 text-right'}>
|
||||
<button className={'btn btn-sm btn-primary'} type={'submit'}>
|
||||
<div css={tw`mt-6 text-right`}>
|
||||
<Button type={'submit'}>
|
||||
{schedule ? 'Save changes' : 'Create schedule'}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Schedule } from '@/api/server/schedules/getServerSchedules';
|
||||
import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
interface Props {
|
||||
schedule: Schedule;
|
||||
|
@ -17,9 +18,9 @@ export default ({ schedule }: Props) => {
|
|||
onDismissed={() => setVisible(false)}
|
||||
/>
|
||||
}
|
||||
<button className={'btn btn-primary btn-sm'} onClick={() => setVisible(true)}>
|
||||
<Button onClick={() => setVisible(true)}>
|
||||
New Task
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,9 @@ import Can from '@/components/elements/Can';
|
|||
import useServer from '@/plugins/useServer';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||
import tw from 'twin.macro';
|
||||
import GreyRowBox from '@/components/elements/GreyRowBox';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
export default ({ match, history }: RouteComponentProps) => {
|
||||
const { uuid } = useServer();
|
||||
|
@ -34,45 +37,38 @@ export default ({ match, history }: RouteComponentProps) => {
|
|||
|
||||
return (
|
||||
<PageContentBlock>
|
||||
<FlashMessageRender byKey={'schedules'} className={'mb-4'}/>
|
||||
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
|
||||
{(!schedules.length && loading) ?
|
||||
<Spinner size={'large'} centered={true}/>
|
||||
<Spinner size={'large'} centered/>
|
||||
:
|
||||
<>
|
||||
{
|
||||
schedules.length === 0 ?
|
||||
<p className={'text-sm text-center text-neutral-400'}>
|
||||
<p css={tw`text-sm text-center text-neutral-400`}>
|
||||
There are no schedules configured for this server.
|
||||
</p>
|
||||
:
|
||||
schedules.map(schedule => (
|
||||
<a
|
||||
<GreyRowBox
|
||||
as={'a'}
|
||||
key={schedule.id}
|
||||
href={`${match.url}/${schedule.id}`}
|
||||
className={'grey-row-box cursor-pointer mb-2'}
|
||||
onClick={e => {
|
||||
css={tw`cursor-pointer mb-2`}
|
||||
onClick={(e: any) => {
|
||||
e.preventDefault();
|
||||
history.push(`${match.url}/${schedule.id}`, { schedule });
|
||||
}}
|
||||
>
|
||||
<ScheduleRow schedule={schedule}/>
|
||||
</a>
|
||||
</GreyRowBox>
|
||||
))
|
||||
}
|
||||
<Can action={'schedule.create'}>
|
||||
<div className={'mt-8 flex justify-end'}>
|
||||
{visible && <EditScheduleModal
|
||||
appear={true}
|
||||
visible={true}
|
||||
onDismissed={() => setVisible(false)}
|
||||
/>}
|
||||
<button
|
||||
type={'button'}
|
||||
className={'btn btn-sm btn-primary'}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
<div css={tw`mt-8 flex justify-end`}>
|
||||
{visible && <EditScheduleModal appear visible onDismissed={() => setVisible(false)}/>}
|
||||
<Button type={'button'} onClick={() => setVisible(true)}>
|
||||
Create schedule
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Can>
|
||||
</>
|
||||
|
|
|
@ -15,6 +15,9 @@ import useServer from '@/plugins/useServer';
|
|||
import useFlash from '@/plugins/useFlash';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import GreyRowBox from '@/components/elements/GreyRowBox';
|
||||
|
||||
interface Params {
|
||||
id: string;
|
||||
|
@ -51,22 +54,22 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
|
|||
|
||||
return (
|
||||
<PageContentBlock>
|
||||
<FlashMessageRender byKey={'schedules'} className={'mb-4'}/>
|
||||
<FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
|
||||
{!schedule || isLoading ?
|
||||
<Spinner size={'large'} centered={true}/>
|
||||
<Spinner size={'large'} centered/>
|
||||
:
|
||||
<>
|
||||
<div className={'grey-row-box'}>
|
||||
<GreyRowBox>
|
||||
<ScheduleRow schedule={schedule}/>
|
||||
</div>
|
||||
</GreyRowBox>
|
||||
<EditScheduleModal
|
||||
visible={showEditModal}
|
||||
schedule={schedule}
|
||||
onDismissed={() => setShowEditModal(false)}
|
||||
/>
|
||||
<div className={'flex items-center mt-8 mb-4'}>
|
||||
<div className={'flex-1'}>
|
||||
<h2>Configured Tasks</h2>
|
||||
<div css={tw`flex items-center mt-8 mb-4`}>
|
||||
<div css={tw`flex-1`}>
|
||||
<h2 css={tw`text-2xl`}>Configured Tasks</h2>
|
||||
</div>
|
||||
</div>
|
||||
{schedule.tasks.length > 0 ?
|
||||
|
@ -79,17 +82,17 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
|
|||
))
|
||||
}
|
||||
{schedule.tasks.length > 1 &&
|
||||
<p className={'text-xs text-neutral-400'}>
|
||||
<p css={tw`text-xs text-neutral-400`}>
|
||||
Task delays are relative to the previous task in the listing.
|
||||
</p>
|
||||
}
|
||||
</>
|
||||
:
|
||||
<p className={'text-sm text-neutral-400'}>
|
||||
<p css={tw`text-sm text-neutral-400`}>
|
||||
There are no tasks configured for this schedule.
|
||||
</p>
|
||||
}
|
||||
<div className={'mt-8 flex justify-end'}>
|
||||
<div css={tw`mt-8 flex justify-end`}>
|
||||
<Can action={'schedule.delete'}>
|
||||
<DeleteScheduleButton
|
||||
scheduleId={schedule.id}
|
||||
|
@ -97,9 +100,9 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
|
|||
/>
|
||||
</Can>
|
||||
<Can action={'schedule.update'}>
|
||||
<button className={'btn btn-primary btn-sm mr-4'} onClick={() => setShowEditModal(true)}>
|
||||
<Button css={tw`mr-4`} onClick={() => setShowEditModal(true)}>
|
||||
Edit
|
||||
</button>
|
||||
</Button>
|
||||
<NewTaskButton schedule={schedule}/>
|
||||
</Can>
|
||||
</div>
|
||||
|
|
|
@ -4,47 +4,48 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||
import { faCalendarAlt } from '@fortawesome/free-solid-svg-icons/faCalendarAlt';
|
||||
import format from 'date-fns/format';
|
||||
import classNames from 'classnames';
|
||||
import tw from 'twin.macro';
|
||||
|
||||
export default ({ schedule }: { schedule: Schedule }) => (
|
||||
<>
|
||||
<div className={'icon'}>
|
||||
<FontAwesomeIcon icon={faCalendarAlt} fixedWidth={true}/>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faCalendarAlt} fixedWidth/>
|
||||
</div>
|
||||
<div className={'flex-1 ml-4'}>
|
||||
<div css={tw`flex-1 ml-4`}>
|
||||
<p>{schedule.name}</p>
|
||||
<p className={'text-xs text-neutral-400'}>
|
||||
<p css={tw`text-xs text-neutral-400`}>
|
||||
Last run
|
||||
at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM Do [at] h:mma') : 'never'}
|
||||
</p>
|
||||
</div>
|
||||
<div className={'flex items-center mx-8'}>
|
||||
<div css={tw`flex items-center mx-8`}>
|
||||
<div>
|
||||
<p className={'font-medium text-center'}>{schedule.cron.minute}</p>
|
||||
<p className={'text-2xs text-neutral-500 uppercase'}>Minute</p>
|
||||
<p css={tw`font-medium text-center`}>{schedule.cron.minute}</p>
|
||||
<p css={tw`text-2xs text-neutral-500 uppercase`}>Minute</p>
|
||||
</div>
|
||||
<div className={'ml-4'}>
|
||||
<p className={'font-medium text-center'}>{schedule.cron.hour}</p>
|
||||
<p className={'text-2xs text-neutral-500 uppercase'}>Hour</p>
|
||||
<div css={tw`ml-4`}>
|
||||
<p css={tw`font-medium text-center`}>{schedule.cron.hour}</p>
|
||||
<p css={tw`text-2xs text-neutral-500 uppercase`}>Hour</p>
|
||||
</div>
|
||||
<div className={'ml-4'}>
|
||||
<p className={'font-medium text-center'}>{schedule.cron.dayOfMonth}</p>
|
||||
<p className={'text-2xs text-neutral-500 uppercase'}>Day (Month)</p>
|
||||
<div css={tw`ml-4`}>
|
||||
<p css={tw`font-medium text-center`}>{schedule.cron.dayOfMonth}</p>
|
||||
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Month)</p>
|
||||
</div>
|
||||
<div className={'ml-4'}>
|
||||
<p className={'font-medium text-center'}>*</p>
|
||||
<p className={'text-2xs text-neutral-500 uppercase'}>Month</p>
|
||||
<div css={tw`ml-4`}>
|
||||
<p css={tw`font-medium text-center`}>*</p>
|
||||
<p css={tw`text-2xs text-neutral-500 uppercase`}>Month</p>
|
||||
</div>
|
||||
<div className={'ml-4'}>
|
||||
<p className={'font-medium text-center'}>{schedule.cron.dayOfWeek}</p>
|
||||
<p className={'text-2xs text-neutral-500 uppercase'}>Day (Week)</p>
|
||||
<div css={tw`ml-4`}>
|
||||
<p css={tw`font-medium text-center`}>{schedule.cron.dayOfWeek}</p>
|
||||
<p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Week)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className={classNames('py-1 px-3 rounded text-xs uppercase', {
|
||||
'bg-green-600': schedule.isActive,
|
||||
'bg-neutral-400': !schedule.isActive,
|
||||
})}
|
||||
css={[
|
||||
tw`py-1 px-3 rounded text-xs uppercase text-white`,
|
||||
schedule.isActive ? tw`bg-green-600` : tw`bg-neutral-400`,
|
||||
]}
|
||||
>
|
||||
{schedule.isActive ? 'Active' : 'Inactive'}
|
||||
</p>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt';
|
||||
import { faCode } from '@fortawesome/free-solid-svg-icons/faCode';
|
||||
import { faToggleOn } from '@fortawesome/free-solid-svg-icons/faToggleOn';
|
||||
import ConfirmTaskDeletionModal from '@/components/server/schedules/ConfirmTaskDeletionModal';
|
||||
import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask';
|
||||
import { httpErrorToHuman } from '@/api/http';
|
||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
|
@ -15,6 +14,8 @@ import useServer from '@/plugins/useServer';
|
|||
import useFlash from '@/plugins/useFlash';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { faFileArchive } from '@fortawesome/free-solid-svg-icons/faFileArchive';
|
||||
import tw from 'twin.macro';
|
||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||
|
||||
interface Props {
|
||||
schedule: Schedule;
|
||||
|
@ -23,14 +24,14 @@ interface Props {
|
|||
|
||||
const getActionDetails = (action: string): [ string, any ] => {
|
||||
switch (action) {
|
||||
case 'command':
|
||||
return ['Send Command', faCode];
|
||||
case 'power':
|
||||
return ['Send Power Action', faToggleOn];
|
||||
case 'backup':
|
||||
return ['Create Backup', faFileArchive];
|
||||
default:
|
||||
return ['Unknown Action', faCode];
|
||||
case 'command':
|
||||
return [ 'Send Command', faCode ];
|
||||
case 'power':
|
||||
return [ 'Send Power Action', faToggleOn ];
|
||||
case 'backup':
|
||||
return [ 'Create Backup', faFileArchive ];
|
||||
default:
|
||||
return [ 'Unknown Action', faCode ];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -60,38 +61,43 @@ export default ({ schedule, task }: Props) => {
|
|||
const [ title, icon ] = getActionDetails(task.action);
|
||||
|
||||
return (
|
||||
<div className={'flex items-center bg-neutral-700 border border-neutral-600 mb-2 px-6 py-4 rounded'}>
|
||||
<div css={tw`flex items-center bg-neutral-700 border border-neutral-600 mb-2 px-6 py-4 rounded`}>
|
||||
<SpinnerOverlay visible={isLoading} fixed={true} size={'large'}/>
|
||||
{isEditing && <TaskDetailsModal
|
||||
schedule={schedule}
|
||||
task={task}
|
||||
onDismissed={() => setIsEditing(false)}
|
||||
/>}
|
||||
<ConfirmTaskDeletionModal
|
||||
<ConfirmationModal
|
||||
title={'Confirm task deletion'}
|
||||
buttonText={'Delete Task'}
|
||||
onConfirmed={onConfirmDeletion}
|
||||
visible={visible}
|
||||
onDismissed={() => setVisible(false)}
|
||||
onConfirmed={() => onConfirmDeletion()}
|
||||
/>
|
||||
<FontAwesomeIcon icon={icon} className={'text-lg text-white'}/>
|
||||
<div className={'flex-1'}>
|
||||
<p className={'ml-6 text-neutral-300 uppercase text-xs'}>
|
||||
>
|
||||
Are you sure you want to delete this task? This action cannot be undone.
|
||||
</ConfirmationModal>
|
||||
<FontAwesomeIcon icon={icon} css={tw`text-lg text-white`}/>
|
||||
<div css={tw`flex-1`}>
|
||||
<p css={tw`ml-6 text-neutral-300 uppercase text-xs`}>
|
||||
{title}
|
||||
</p>
|
||||
{task.payload &&
|
||||
<div className={'ml-6 mt-2'}>
|
||||
{task.action === 'backup' && <p className={'text-xs uppercase text-neutral-400 mb-1'}>Ignoring files & folders:</p>}
|
||||
<div className={'font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto whitespace-pre inline-block'}>
|
||||
<div css={tw`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 whitespace-pre inline-block`}>
|
||||
{task.payload}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{task.sequenceId > 1 &&
|
||||
<div className={'mr-6'}>
|
||||
<p className={'text-center mb-1'}>
|
||||
<div css={tw`mr-6`}>
|
||||
<p css={tw`text-center mb-1`}>
|
||||
{task.timeOffset}s
|
||||
</p>
|
||||
<p className={'text-neutral-300 uppercase text-2xs'}>
|
||||
<p css={tw`text-neutral-300 uppercase text-2xs`}>
|
||||
Delay Run By
|
||||
</p>
|
||||
</div>
|
||||
|
@ -100,7 +106,7 @@ export default ({ schedule, task }: Props) => {
|
|||
<button
|
||||
type={'button'}
|
||||
aria-label={'Edit scheduled task'}
|
||||
className={'block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mr-4'}
|
||||
css={tw`block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mr-4`}
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencilAlt}/>
|
||||
|
@ -110,7 +116,7 @@ export default ({ schedule, task }: Props) => {
|
|||
<button
|
||||
type={'button'}
|
||||
aria-label={'Delete scheduled task'}
|
||||
className={'block text-sm p-2 text-neutral-500 hover:text-red-600 transition-colors duration-150'}
|
||||
css={tw`block text-sm p-2 text-neutral-500 hover:text-red-600 transition-colors duration-150`}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt}/>
|
||||
|
|
|
@ -11,6 +11,11 @@ import { number, object, string } from 'yup';
|
|||
import useFlash from '@/plugins/useFlash';
|
||||
import useServer from '@/plugins/useServer';
|
||||
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';
|
||||
import Select from '@/components/elements/Select';
|
||||
|
||||
interface Props {
|
||||
schedule: Schedule;
|
||||
|
@ -35,20 +40,20 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
|||
}, [ action ]);
|
||||
|
||||
return (
|
||||
<Form className={'m-0'}>
|
||||
<h3 className={'mb-6'}>{isEditingTask ? 'Edit Task' : 'Create Task'}</h3>
|
||||
<div className={'flex'}>
|
||||
<div className={'mr-2 w-1/3'}>
|
||||
<label className={'input-dark-label'}>Action</label>
|
||||
<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'} className={'input-dark'}>
|
||||
<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 className={'flex-1'}>
|
||||
<div css={tw`flex-1`}>
|
||||
{action === 'command' ?
|
||||
<Field
|
||||
name={'payload'}
|
||||
|
@ -58,9 +63,9 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
|||
:
|
||||
action === 'power' ?
|
||||
<div>
|
||||
<label className={'input-dark-label'}>Payload</label>
|
||||
<Label>Payload</Label>
|
||||
<FormikFieldWrapper name={'payload'}>
|
||||
<FormikField as={'select'} name={'payload'} className={'input-dark'}>
|
||||
<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>
|
||||
|
@ -70,28 +75,28 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
|||
</div>
|
||||
:
|
||||
<div>
|
||||
<label className={'input-dark-label'}>Ignored Files</label>
|
||||
<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.'}
|
||||
>
|
||||
<FormikField as={'textarea'} name={'payload'} className={'input-dark h-32'}/>
|
||||
<FormikField as={Textarea} name={'payload'} css={tw`h-32`}/>
|
||||
</FormikFieldWrapper>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<div css={tw`mt-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 className={'flex justify-end mt-6'}>
|
||||
<button type={'submit'} className={'btn btn-primary btn-sm'}>
|
||||
<div css={tw`flex justify-end mt-6`}>
|
||||
<Button type={'submit'}>
|
||||
{isEditingTask ? 'Save Changes' : 'Create Task'}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
@ -148,12 +153,12 @@ export default ({ task, schedule, onDismissed }: Props) => {
|
|||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Modal
|
||||
visible={true}
|
||||
appear={true}
|
||||
visible
|
||||
appear
|
||||
onDismissed={() => onDismissed()}
|
||||
showSpinnerOverlay={isSubmitting}
|
||||
>
|
||||
<FlashMessageRender byKey={'schedule:task'} className={'mb-4'}/>
|
||||
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`}/>
|
||||
<TaskDetailsForm isEditingTask={typeof task !== 'undefined'}/>
|
||||
</Modal>
|
||||
)}
|
||||
|
|
Loading…
Reference in a new issue