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} |         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()}> | ||||||
|  | |||||||
							
								
								
									
										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 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> | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -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 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> | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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> | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -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> | ||||||
|                 </> |                 </> | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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}/> | ||||||
|  | |||||||
| @ -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> | ||||||
|             )} |             )} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dane Everitt
						Dane Everitt