Update schedule page

This commit is contained in:
Dane Everitt 2020-07-04 17:00:19 -07:00
parent f3586056f4
commit a288374027
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
12 changed files with 180 additions and 168 deletions

View File

@ -18,7 +18,7 @@ const ConfirmationModal = ({ title, appear, children, visible, buttonText, onCon
showSpinnerOverlay={showSpinnerOverlay} showSpinnerOverlay={showSpinnerOverlay}
onDismissed={() => onDismissed()} 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> <p css={tw`text-sm`}>{children}</p>
<div css={tw`flex items-center justify-end mt-8`}> <div css={tw`flex items-center justify-end mt-8`}>
<Button isSecondary onClick={() => onDismissed()}> <Button isSecondary onClick={() => onDismissed()}>

View 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;

View File

@ -2,6 +2,8 @@ import React, { useMemo } from 'react';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import v4 from 'uuid/v4'; import v4 from 'uuid/v4';
import tw from 'twin.macro'; import tw from 'twin.macro';
import Label from '@/components/elements/Label';
import Input from '@/components/elements/Input';
const ToggleContainer = styled.div` const ToggleContainer = styled.div`
${tw`relative select-none w-12 leading-normal`}; ${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`}> <div css={tw`flex items-center`}>
<ToggleContainer css={tw`flex-none`}> <ToggleContainer css={tw`flex-none`}>
{children {children
|| <input || <Input
id={uuid} id={uuid}
name={name} name={name}
type={'checkbox'} type={'checkbox'}
@ -58,21 +60,20 @@ const Switch = ({ name, label, description, defaultChecked, onChange, children }
defaultChecked={defaultChecked} defaultChecked={defaultChecked}
/> />
} }
<label htmlFor={uuid}/> <Label htmlFor={uuid}/>
</ToggleContainer> </ToggleContainer>
{(label || description) && {(label || description) &&
<div css={tw`ml-4 w-full`}> <div css={tw`ml-4 w-full`}>
{label && {label &&
<label <Label
css={[ tw`cursor-pointer`, !!description && tw`mb-0` ]} css={[ tw`cursor-pointer`, !!description && tw`mb-0` ]}
className={'input-dark-label'}
htmlFor={uuid} htmlFor={uuid}
> >
{label} {label}
</label> </Label>
} }
{description && {description &&
<p className={'input-help'}> <p css={tw`text-neutral-400 text-sm mt-2`}>
{description} {description}
</p> </p>
} }

View File

@ -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>
);

View File

@ -1,10 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Modal from '@/components/elements/Modal';
import deleteSchedule from '@/api/server/schedules/deleteSchedule'; import deleteSchedule from '@/api/server/schedules/deleteSchedule';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { Actions, useStoreActions } from 'easy-peasy'; import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import ConfirmationModal from '@/components/elements/ConfirmationModal';
interface Props { interface Props {
scheduleId: number; scheduleId: number;
@ -36,34 +38,19 @@ export default ({ scheduleId, onDeleted }: Props) => {
return ( return (
<> <>
<Modal <ConfirmationModal
title={'Delete schedule?'}
buttonText={'Yes, delete schedule'}
onConfirmed={onDelete}
visible={visible} visible={visible}
onDismissed={() => setVisible(false)} onDismissed={() => setVisible(false)}
showSpinnerOverlay={isLoading}
> >
<h3 className={'mb-6'}>Delete schedule</h3> Are you sure you want to delete this schedule? All tasks will be removed and any running processes
<p className={'text-sm'}> will be terminated.
Are you sure you want to delete this schedule? All tasks will be removed and any running processes </ConfirmationModal>
will be terminated. <Button css={tw`mr-4`} color={'red'} isSecondary onClick={() => setVisible(true)}>
</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)}>
Delete Delete
</button> </Button>
</> </>
); );
}; };

View File

@ -10,6 +10,8 @@ import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
type Props = { type Props = {
schedule?: Schedule; schedule?: Schedule;
@ -29,43 +31,43 @@ const EditScheduleModal = ({ schedule, ...props }: Omit<Props, 'onScheduleUpdate
return ( return (
<Modal {...props} showSpinnerOverlay={isSubmitting}> <Modal {...props} showSpinnerOverlay={isSubmitting}>
<h3 className={'mb-6'}>{schedule ? 'Edit schedule' : 'Create new schedule'}</h3> <h3 css={tw`mb-6`}>{schedule ? 'Edit schedule' : 'Create new schedule'}</h3>
<FlashMessageRender byKey={'schedule:edit'} className={'mb-6'}/> <FlashMessageRender byKey={'schedule:edit'} css={tw`mb-6`}/>
<Form> <Form>
<Field <Field
name={'name'} name={'name'}
label={'Schedule name'} label={'Schedule name'}
description={'A human readable identifer for this schedule.'} description={'A human readable identifer for this schedule.'}
/> />
<div className={'flex mt-6'}> <div css={tw`flex mt-6`}>
<div className={'flex-1 mr-4'}> <div css={tw`flex-1 mr-4`}>
<Field name={'dayOfWeek'} label={'Day of week'}/> <Field name={'dayOfWeek'} label={'Day of week'}/>
</div> </div>
<div className={'flex-1 mr-4'}> <div css={tw`flex-1 mr-4`}>
<Field name={'dayOfMonth'} label={'Day of month'}/> <Field name={'dayOfMonth'} label={'Day of month'}/>
</div> </div>
<div className={'flex-1 mr-4'}> <div css={tw`flex-1 mr-4`}>
<Field name={'hour'} label={'Hour'}/> <Field name={'hour'} label={'Hour'}/>
</div> </div>
<div className={'flex-1'}> <div css={tw`flex-1`}>
<Field name={'minute'} label={'Minute'}/> <Field name={'minute'} label={'Minute'}/>
</div> </div>
</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 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. running. Use the fields above to specify when these tasks should begin running.
</p> </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 <FormikSwitch
name={'enabled'} name={'enabled'}
description={'If disabled, this schedule and it\'s associated tasks will not run.'} description={'If disabled, this schedule and it\'s associated tasks will not run.'}
label={'Enabled'} label={'Enabled'}
/> />
</div> </div>
<div className={'mt-6 text-right'}> <div css={tw`mt-6 text-right`}>
<button className={'btn btn-sm btn-primary'} type={'submit'}> <Button type={'submit'}>
{schedule ? 'Save changes' : 'Create schedule'} {schedule ? 'Save changes' : 'Create schedule'}
</button> </Button>
</div> </div>
</Form> </Form>
</Modal> </Modal>

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Schedule } from '@/api/server/schedules/getServerSchedules'; import { Schedule } from '@/api/server/schedules/getServerSchedules';
import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal'; import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal';
import Button from '@/components/elements/Button';
interface Props { interface Props {
schedule: Schedule; schedule: Schedule;
@ -17,9 +18,9 @@ export default ({ schedule }: Props) => {
onDismissed={() => setVisible(false)} onDismissed={() => setVisible(false)}
/> />
} }
<button className={'btn btn-primary btn-sm'} onClick={() => setVisible(true)}> <Button onClick={() => setVisible(true)}>
New Task New Task
</button> </Button>
</> </>
); );
}; };

View File

@ -11,6 +11,9 @@ import Can from '@/components/elements/Can';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import PageContentBlock from '@/components/elements/PageContentBlock'; 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) => { export default ({ match, history }: RouteComponentProps) => {
const { uuid } = useServer(); const { uuid } = useServer();
@ -34,45 +37,38 @@ export default ({ match, history }: RouteComponentProps) => {
return ( return (
<PageContentBlock> <PageContentBlock>
<FlashMessageRender byKey={'schedules'} className={'mb-4'}/> <FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
{(!schedules.length && loading) ? {(!schedules.length && loading) ?
<Spinner size={'large'} centered={true}/> <Spinner size={'large'} centered/>
: :
<> <>
{ {
schedules.length === 0 ? 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. There are no schedules configured for this server.
</p> </p>
: :
schedules.map(schedule => ( schedules.map(schedule => (
<a <GreyRowBox
as={'a'}
key={schedule.id} key={schedule.id}
href={`${match.url}/${schedule.id}`} href={`${match.url}/${schedule.id}`}
className={'grey-row-box cursor-pointer mb-2'} css={tw`cursor-pointer mb-2`}
onClick={e => { onClick={(e: any) => {
e.preventDefault(); e.preventDefault();
history.push(`${match.url}/${schedule.id}`, { schedule }); history.push(`${match.url}/${schedule.id}`, { schedule });
}} }}
> >
<ScheduleRow schedule={schedule}/> <ScheduleRow schedule={schedule}/>
</a> </GreyRowBox>
)) ))
} }
<Can action={'schedule.create'}> <Can action={'schedule.create'}>
<div className={'mt-8 flex justify-end'}> <div css={tw`mt-8 flex justify-end`}>
{visible && <EditScheduleModal {visible && <EditScheduleModal appear visible onDismissed={() => setVisible(false)}/>}
appear={true} <Button type={'button'} onClick={() => setVisible(true)}>
visible={true}
onDismissed={() => setVisible(false)}
/>}
<button
type={'button'}
className={'btn btn-sm btn-primary'}
onClick={() => setVisible(true)}
>
Create schedule Create schedule
</button> </Button>
</div> </div>
</Can> </Can>
</> </>

View File

@ -15,6 +15,9 @@ import useServer from '@/plugins/useServer';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import PageContentBlock from '@/components/elements/PageContentBlock'; 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 { interface Params {
id: string; id: string;
@ -51,22 +54,22 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
return ( return (
<PageContentBlock> <PageContentBlock>
<FlashMessageRender byKey={'schedules'} className={'mb-4'}/> <FlashMessageRender byKey={'schedules'} css={tw`mb-4`}/>
{!schedule || isLoading ? {!schedule || isLoading ?
<Spinner size={'large'} centered={true}/> <Spinner size={'large'} centered/>
: :
<> <>
<div className={'grey-row-box'}> <GreyRowBox>
<ScheduleRow schedule={schedule}/> <ScheduleRow schedule={schedule}/>
</div> </GreyRowBox>
<EditScheduleModal <EditScheduleModal
visible={showEditModal} visible={showEditModal}
schedule={schedule} schedule={schedule}
onDismissed={() => setShowEditModal(false)} onDismissed={() => setShowEditModal(false)}
/> />
<div className={'flex items-center mt-8 mb-4'}> <div css={tw`flex items-center mt-8 mb-4`}>
<div className={'flex-1'}> <div css={tw`flex-1`}>
<h2>Configured Tasks</h2> <h2 css={tw`text-2xl`}>Configured Tasks</h2>
</div> </div>
</div> </div>
{schedule.tasks.length > 0 ? {schedule.tasks.length > 0 ?
@ -79,17 +82,17 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
)) ))
} }
{schedule.tasks.length > 1 && {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. Task delays are relative to the previous task in the listing.
</p> </p>
} }
</> </>
: :
<p className={'text-sm text-neutral-400'}> <p css={tw`text-sm text-neutral-400`}>
There are no tasks configured for this schedule. There are no tasks configured for this schedule.
</p> </p>
} }
<div className={'mt-8 flex justify-end'}> <div css={tw`mt-8 flex justify-end`}>
<Can action={'schedule.delete'}> <Can action={'schedule.delete'}>
<DeleteScheduleButton <DeleteScheduleButton
scheduleId={schedule.id} scheduleId={schedule.id}
@ -97,9 +100,9 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
/> />
</Can> </Can>
<Can action={'schedule.update'}> <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 Edit
</button> </Button>
<NewTaskButton schedule={schedule}/> <NewTaskButton schedule={schedule}/>
</Can> </Can>
</div> </div>

View File

@ -4,47 +4,48 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCalendarAlt } from '@fortawesome/free-solid-svg-icons/faCalendarAlt'; import { faCalendarAlt } from '@fortawesome/free-solid-svg-icons/faCalendarAlt';
import format from 'date-fns/format'; import format from 'date-fns/format';
import classNames from 'classnames'; import classNames from 'classnames';
import tw from 'twin.macro';
export default ({ schedule }: { schedule: Schedule }) => ( export default ({ schedule }: { schedule: Schedule }) => (
<> <>
<div className={'icon'}> <div>
<FontAwesomeIcon icon={faCalendarAlt} fixedWidth={true}/> <FontAwesomeIcon icon={faCalendarAlt} fixedWidth/>
</div> </div>
<div className={'flex-1 ml-4'}> <div css={tw`flex-1 ml-4`}>
<p>{schedule.name}</p> <p>{schedule.name}</p>
<p className={'text-xs text-neutral-400'}> <p css={tw`text-xs text-neutral-400`}>
Last run Last run
at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM Do [at] h:mma') : 'never'} at: {schedule.lastRunAt ? format(schedule.lastRunAt, 'MMM Do [at] h:mma') : 'never'}
</p> </p>
</div> </div>
<div className={'flex items-center mx-8'}> <div css={tw`flex items-center mx-8`}>
<div> <div>
<p className={'font-medium text-center'}>{schedule.cron.minute}</p> <p css={tw`font-medium text-center`}>{schedule.cron.minute}</p>
<p className={'text-2xs text-neutral-500 uppercase'}>Minute</p> <p css={tw`text-2xs text-neutral-500 uppercase`}>Minute</p>
</div> </div>
<div className={'ml-4'}> <div css={tw`ml-4`}>
<p className={'font-medium text-center'}>{schedule.cron.hour}</p> <p css={tw`font-medium text-center`}>{schedule.cron.hour}</p>
<p className={'text-2xs text-neutral-500 uppercase'}>Hour</p> <p css={tw`text-2xs text-neutral-500 uppercase`}>Hour</p>
</div> </div>
<div className={'ml-4'}> <div css={tw`ml-4`}>
<p className={'font-medium text-center'}>{schedule.cron.dayOfMonth}</p> <p css={tw`font-medium text-center`}>{schedule.cron.dayOfMonth}</p>
<p className={'text-2xs text-neutral-500 uppercase'}>Day (Month)</p> <p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Month)</p>
</div> </div>
<div className={'ml-4'}> <div css={tw`ml-4`}>
<p className={'font-medium text-center'}>*</p> <p css={tw`font-medium text-center`}>*</p>
<p className={'text-2xs text-neutral-500 uppercase'}>Month</p> <p css={tw`text-2xs text-neutral-500 uppercase`}>Month</p>
</div> </div>
<div className={'ml-4'}> <div css={tw`ml-4`}>
<p className={'font-medium text-center'}>{schedule.cron.dayOfWeek}</p> <p css={tw`font-medium text-center`}>{schedule.cron.dayOfWeek}</p>
<p className={'text-2xs text-neutral-500 uppercase'}>Day (Week)</p> <p css={tw`text-2xs text-neutral-500 uppercase`}>Day (Week)</p>
</div> </div>
</div> </div>
<div> <div>
<p <p
className={classNames('py-1 px-3 rounded text-xs uppercase', { css={[
'bg-green-600': schedule.isActive, tw`py-1 px-3 rounded text-xs uppercase text-white`,
'bg-neutral-400': !schedule.isActive, schedule.isActive ? tw`bg-green-600` : tw`bg-neutral-400`,
})} ]}
> >
{schedule.isActive ? 'Active' : 'Inactive'} {schedule.isActive ? 'Active' : 'Inactive'}
</p> </p>

View File

@ -4,7 +4,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt';
import { faCode } from '@fortawesome/free-solid-svg-icons/faCode'; import { faCode } from '@fortawesome/free-solid-svg-icons/faCode';
import { faToggleOn } from '@fortawesome/free-solid-svg-icons/faToggleOn'; import { faToggleOn } from '@fortawesome/free-solid-svg-icons/faToggleOn';
import ConfirmTaskDeletionModal from '@/components/server/schedules/ConfirmTaskDeletionModal';
import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask'; import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
@ -15,6 +14,8 @@ import useServer from '@/plugins/useServer';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { faFileArchive } from '@fortawesome/free-solid-svg-icons/faFileArchive'; import { faFileArchive } from '@fortawesome/free-solid-svg-icons/faFileArchive';
import tw from 'twin.macro';
import ConfirmationModal from '@/components/elements/ConfirmationModal';
interface Props { interface Props {
schedule: Schedule; schedule: Schedule;
@ -23,14 +24,14 @@ interface Props {
const getActionDetails = (action: string): [ string, any ] => { const getActionDetails = (action: string): [ string, any ] => {
switch (action) { switch (action) {
case 'command': case 'command':
return ['Send Command', faCode]; return [ 'Send Command', faCode ];
case 'power': case 'power':
return ['Send Power Action', faToggleOn]; return [ 'Send Power Action', faToggleOn ];
case 'backup': case 'backup':
return ['Create Backup', faFileArchive]; return [ 'Create Backup', faFileArchive ];
default: default:
return ['Unknown Action', faCode]; return [ 'Unknown Action', faCode ];
} }
}; };
@ -60,38 +61,43 @@ export default ({ schedule, task }: Props) => {
const [ title, icon ] = getActionDetails(task.action); const [ title, icon ] = getActionDetails(task.action);
return ( 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'}/> <SpinnerOverlay visible={isLoading} fixed={true} size={'large'}/>
{isEditing && <TaskDetailsModal {isEditing && <TaskDetailsModal
schedule={schedule} schedule={schedule}
task={task} task={task}
onDismissed={() => setIsEditing(false)} onDismissed={() => setIsEditing(false)}
/>} />}
<ConfirmTaskDeletionModal <ConfirmationModal
title={'Confirm task deletion'}
buttonText={'Delete Task'}
onConfirmed={onConfirmDeletion}
visible={visible} visible={visible}
onDismissed={() => setVisible(false)} onDismissed={() => setVisible(false)}
onConfirmed={() => onConfirmDeletion()} >
/> Are you sure you want to delete this task? This action cannot be undone.
<FontAwesomeIcon icon={icon} className={'text-lg text-white'}/> </ConfirmationModal>
<div className={'flex-1'}> <FontAwesomeIcon icon={icon} css={tw`text-lg text-white`}/>
<p className={'ml-6 text-neutral-300 uppercase text-xs'}> <div css={tw`flex-1`}>
<p css={tw`ml-6 text-neutral-300 uppercase text-xs`}>
{title} {title}
</p> </p>
{task.payload && {task.payload &&
<div className={'ml-6 mt-2'}> <div css={tw`ml-6 mt-2`}>
{task.action === 'backup' && <p className={'text-xs uppercase text-neutral-400 mb-1'}>Ignoring files & folders:</p>} {task.action === 'backup' &&
<div className={'font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto whitespace-pre inline-block'}> <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} {task.payload}
</div> </div>
</div> </div>
} }
</div> </div>
{task.sequenceId > 1 && {task.sequenceId > 1 &&
<div className={'mr-6'}> <div css={tw`mr-6`}>
<p className={'text-center mb-1'}> <p css={tw`text-center mb-1`}>
{task.timeOffset}s {task.timeOffset}s
</p> </p>
<p className={'text-neutral-300 uppercase text-2xs'}> <p css={tw`text-neutral-300 uppercase text-2xs`}>
Delay Run By Delay Run By
</p> </p>
</div> </div>
@ -100,7 +106,7 @@ export default ({ schedule, task }: Props) => {
<button <button
type={'button'} type={'button'}
aria-label={'Edit scheduled task'} 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)} onClick={() => setIsEditing(true)}
> >
<FontAwesomeIcon icon={faPencilAlt}/> <FontAwesomeIcon icon={faPencilAlt}/>
@ -110,7 +116,7 @@ export default ({ schedule, task }: Props) => {
<button <button
type={'button'} type={'button'}
aria-label={'Delete scheduled task'} 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)} onClick={() => setVisible(true)}
> >
<FontAwesomeIcon icon={faTrashAlt}/> <FontAwesomeIcon icon={faTrashAlt}/>

View File

@ -11,6 +11,11 @@ import { number, object, string } from 'yup';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; 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 { interface Props {
schedule: Schedule; schedule: Schedule;
@ -35,20 +40,20 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
}, [ action ]); }, [ action ]);
return ( return (
<Form className={'m-0'}> <Form css={tw`m-0`}>
<h3 className={'mb-6'}>{isEditingTask ? 'Edit Task' : 'Create Task'}</h3> <h2 css={tw`text-2xl mb-6`}>{isEditingTask ? 'Edit Task' : 'Create Task'}</h2>
<div className={'flex'}> <div css={tw`flex`}>
<div className={'mr-2 w-1/3'}> <div css={tw`mr-2 w-1/3`}>
<label className={'input-dark-label'}>Action</label> <Label>Action</Label>
<FormikFieldWrapper name={'action'}> <FormikFieldWrapper name={'action'}>
<FormikField as={'select'} name={'action'} className={'input-dark'}> <FormikField as={Select} name={'action'}>
<option value={'command'}>Send command</option> <option value={'command'}>Send command</option>
<option value={'power'}>Send power action</option> <option value={'power'}>Send power action</option>
<option value={'backup'}>Create backup</option> <option value={'backup'}>Create backup</option>
</FormikField> </FormikField>
</FormikFieldWrapper> </FormikFieldWrapper>
</div> </div>
<div className={'flex-1'}> <div css={tw`flex-1`}>
{action === 'command' ? {action === 'command' ?
<Field <Field
name={'payload'} name={'payload'}
@ -58,9 +63,9 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
: :
action === 'power' ? action === 'power' ?
<div> <div>
<label className={'input-dark-label'}>Payload</label> <Label>Payload</Label>
<FormikFieldWrapper name={'payload'}> <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={'start'}>Start the server</option>
<option value={'restart'}>Restart the server</option> <option value={'restart'}>Restart the server</option>
<option value={'stop'}>Stop the server</option> <option value={'stop'}>Stop the server</option>
@ -70,28 +75,28 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
</div> </div>
: :
<div> <div>
<label className={'input-dark-label'}>Ignored Files</label> <Label>Ignored Files</Label>
<FormikFieldWrapper <FormikFieldWrapper
name={'payload'} 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.'} 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> </FormikFieldWrapper>
</div> </div>
} }
</div> </div>
</div> </div>
<div className={'mt-6'}> <div css={tw`mt-6`}>
<Field <Field
name={'timeOffset'} name={'timeOffset'}
label={'Time offset (in seconds)'} 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 className={'flex justify-end mt-6'}> <div css={tw`flex justify-end mt-6`}>
<button type={'submit'} className={'btn btn-primary btn-sm'}> <Button type={'submit'}>
{isEditingTask ? 'Save Changes' : 'Create Task'} {isEditingTask ? 'Save Changes' : 'Create Task'}
</button> </Button>
</div> </div>
</Form> </Form>
); );
@ -148,12 +153,12 @@ export default ({ task, schedule, onDismissed }: Props) => {
> >
{({ isSubmitting }) => ( {({ isSubmitting }) => (
<Modal <Modal
visible={true} visible
appear={true} appear
onDismissed={() => onDismissed()} onDismissed={() => onDismissed()}
showSpinnerOverlay={isSubmitting} showSpinnerOverlay={isSubmitting}
> >
<FlashMessageRender byKey={'schedule:task'} className={'mb-4'}/> <FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`}/>
<TaskDetailsForm isEditingTask={typeof task !== 'undefined'}/> <TaskDetailsForm isEditingTask={typeof task !== 'undefined'}/>
</Modal> </Modal>
)} )}