mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-10-31 21:06:52 +01:00 
			
		
		
		
	Update schedule page
This commit is contained in:
		
							parent
							
								
									f3586056f4
								
							
						
					
					
						commit
						a288374027
					
				| @ -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)}> | ||||
|             </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; | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dane Everitt
						Dane Everitt