Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function permission(): string
public function rules(): array
{
return [
'action' => 'required|in:command,power,backup',
'action' => 'required|in:command,power,backup,delete-files',
'payload' => 'required_unless:action,backup|string|nullable',
'time_offset' => 'required|numeric|min:0|max:900',
'sequence_id' => 'sometimes|required|numeric|min:1',
Expand Down
11 changes: 8 additions & 3 deletions app/Jobs/Schedule/RunTaskJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Repositories\Wings\DaemonFileRepository;

class RunTaskJob extends Job implements ShouldQueue
{
Expand All @@ -38,6 +39,7 @@ public function handle(
DaemonCommandRepository $commandRepository,
InitiateBackupService $backupService,
DaemonPowerRepository $powerRepository,
DaemonFileRepository $fileRepository
) {
// Do not process a task that is not set to active, unless it's been manually triggered.
if (!$this->task->schedule->is_active && !$this->manualRun) {
Expand All @@ -61,7 +63,7 @@ public function handle(
// Perform the provided task against the daemon.
try {
switch ($this->task->action) {
case Task::ACTION_POWER:
case Task::ACTION_POWER:
$powerRepository->setServer($server)->send($this->task->payload);
break;
case Task::ACTION_COMMAND:
Expand All @@ -70,6 +72,9 @@ public function handle(
case Task::ACTION_BACKUP:
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true);
break;
case Task::ACTION_DELETE_FILES:
$fileRepository->setServer($server)->deleteFiles('/', explode(PHP_EOL, $this->task->payload));
break;
default:
throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action);
}
Expand All @@ -88,7 +93,7 @@ public function handle(
/**
* Handle a failure while sending the action to the daemon or otherwise processing the job.
*/
public function failed(?\Exception $exception = null)
public function failed(\Exception $exception = null)
{
$this->markTaskNotQueued();
$this->markScheduleComplete();
Expand All @@ -99,7 +104,7 @@ public function failed(?\Exception $exception = null)
*/
private function queueNextTask()
{
/** @var Task|null $nextTask */
/** @var \Pterodactyl\Models\Task|null $nextTask */
$nextTask = Task::query()->where('schedule_id', $this->task->schedule_id)
->orderBy('sequence_id', 'asc')
->where('sequence_id', '>', $this->task->sequence_id)
Expand Down
1 change: 1 addition & 0 deletions app/Models/Task.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Task extends Model
public const ACTION_POWER = 'power';
public const ACTION_COMMAND = 'command';
public const ACTION_BACKUP = 'backup';
public const ACTION_DELETE_FILES = 'delete-files';

/**
* The table associated with the model.
Expand Down
26 changes: 14 additions & 12 deletions resources/scripts/components/server/schedules/ScheduleTaskRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const getActionDetails = (action: string): [string, any] => {
return ['Send Power Action', faToggleOn];
case 'backup':
return ['Create Backup', faFileArchive];
case 'delete-files':
return ['Delete Files', faTrashAlt];
default:
return ['Unknown Action', faCode];
}
Expand Down Expand Up @@ -67,7 +69,7 @@ export default ({ schedule, task }: Props) => {
const [title, icon] = getActionDetails(task.action);

return (
<div css={tw`sm:flex items-center p-3 sm:p-6 border-b border-neutral-800`}>
<div css={tw`items-center p-3 border-b sm:flex sm:p-6 border-neutral-800`}>
<SpinnerOverlay visible={isLoading} fixed size={'large'} />
<TaskDetailsModal
schedule={schedule}
Expand All @@ -84,34 +86,34 @@ export default ({ schedule, task }: Props) => {
>
Are you sure you want to delete this task? This action cannot be undone.
</ConfirmationModal>
<FontAwesomeIcon icon={icon} css={tw`text-lg text-white hidden md:block`} />
<div css={tw`flex-none sm:flex-1 w-full sm:w-auto overflow-x-auto`}>
<p css={tw`md:ml-6 text-neutral-200 uppercase text-sm`}>{title}</p>
<FontAwesomeIcon icon={icon} css={tw`hidden text-lg text-white md:block`} />
<div css={tw`flex-none w-full overflow-x-auto sm:flex-1 sm:w-auto`}>
<p css={tw`text-sm uppercase md:ml-6 text-neutral-200`}>{title}</p>
{task.payload && (
<div css={tw`md:ml-6 mt-2`}>
<div css={tw`mt-2 md:ml-6`}>
{task.action === 'backup' && (
<p css={tw`text-xs uppercase text-neutral-400 mb-1`}>Ignoring files & folders:</p>
<p css={tw`mb-1 text-xs uppercase text-neutral-400`}>Ignoring files & folders:</p>
)}
<div
css={tw`font-mono bg-neutral-800 rounded py-1 px-2 text-sm w-auto inline-block whitespace-pre-wrap break-all`}
css={tw`inline-block w-auto px-2 py-1 font-mono text-sm break-all whitespace-pre-wrap rounded bg-neutral-800`}
>
{task.payload}
</div>
</div>
)}
</div>
<div css={tw`mt-3 sm:mt-0 flex items-center w-full sm:w-auto`}>
<div css={tw`flex items-center w-full mt-3 sm:mt-0 sm:w-auto`}>
{task.continueOnFailure && (
<div css={tw`mr-6`}>
<div css={tw`flex items-center px-2 py-1 bg-yellow-500 text-yellow-800 text-sm rounded-full`}>
<div css={tw`flex items-center px-2 py-1 text-sm text-yellow-800 bg-yellow-500 rounded-full`}>
<Icon icon={faArrowCircleDown} css={tw`w-3 h-3 mr-2`} />
Continues on Failure
</div>
</div>
)}
{task.sequenceId > 1 && task.timeOffset > 0 && (
<div css={tw`mr-6`}>
<div css={tw`flex items-center px-2 py-1 bg-neutral-500 text-sm rounded-full`}>
<div css={tw`flex items-center px-2 py-1 text-sm rounded-full bg-neutral-500`}>
<Icon icon={faClock} css={tw`w-3 h-3 mr-2`} />
{task.timeOffset}s later
</div>
Expand All @@ -121,7 +123,7 @@ export default ({ schedule, task }: Props) => {
<button
type={'button'}
aria-label={'Edit scheduled task'}
css={tw`block text-sm p-2 text-neutral-500 hover:text-neutral-100 transition-colors duration-150 mr-4 ml-auto sm:ml-0`}
css={tw`block p-2 ml-auto mr-4 text-sm transition-colors duration-150 text-neutral-500 hover:text-neutral-100 sm:ml-0`}
onClick={() => setIsEditing(true)}
>
<FontAwesomeIcon icon={faPencilAlt} />
Expand All @@ -131,7 +133,7 @@ export default ({ schedule, task }: Props) => {
<button
type={'button'}
aria-label={'Delete scheduled task'}
css={tw`block text-sm p-2 text-neutral-500 hover:text-red-600 transition-colors duration-150`}
css={tw`block p-2 text-sm transition-colors duration-150 text-neutral-500 hover:text-red-600`}
onClick={() => setVisible(true)}
>
<FontAwesomeIcon icon={faTrashAlt} />
Expand Down
23 changes: 18 additions & 5 deletions resources/scripts/components/server/schedules/TaskDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface Values {
}

const schema = object().shape({
action: string().required().oneOf(['command', 'power', 'backup']),
action: string().required().oneOf(['command', 'power', 'backup', 'delete-files']),
payload: string().when('action', {
is: (v) => v !== 'backup',
then: string().required('A task payload must be provided.'),
Expand Down Expand Up @@ -119,16 +119,17 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
{({ isSubmitting, values }) => (
<Form css={tw`m-0`}>
<FlashMessageRender byKey={'schedule:task'} css={tw`mb-4`} />
<h2 css={tw`text-2xl mb-6`}>{task ? 'Edit Task' : 'Create Task'}</h2>
<h2 css={tw`mb-6 text-2xl`}>{task ? 'Edit Task' : 'Create Task'}</h2>
<div css={tw`flex`}>
<div css={tw`mr-2 w-1/3`}>
<div css={tw`w-1/3 mr-2`}>
<Label>Action</Label>
<ActionListener />
<FormikFieldWrapper name={'action'}>
<FormikField as={Select} name={'action'}>
<option value={'command'}>Send command</option>
<option value={'power'}>Send power action</option>
<option value={'backup'}>Create backup</option>
<option value={'delete-files'}>Delete files</option>
</FormikField>
</FormikFieldWrapper>
</div>
Expand Down Expand Up @@ -162,7 +163,7 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
</FormikField>
</FormikFieldWrapper>
</div>
) : (
) : values.action === 'backup' ? (
<div>
<Label>Ignored Files</Label>
<FormikFieldWrapper
Expand All @@ -174,9 +175,21 @@ const TaskDetailsModal = ({ schedule, task }: Props) => {
<FormikField as={Textarea} name={'payload'} rows={6} />
</FormikFieldWrapper>
</div>
) : (
<div>
<Label>Files</Label>
<FormikFieldWrapper
name={'payload'}
description={
'Include the files and folders to be deleted when this task runs, one per line. Be careful when using this option as deleted files cannot be recovered. Only files with exact patterns will be deleted.'
}
>
<FormikField as={Textarea} name={'payload'} rows={6} />
</FormikFieldWrapper>
</div>
)}
</div>
<div css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
<div css={tw`p-4 mt-6 border rounded shadow-inner bg-neutral-700 border-neutral-800`}>
<FormikSwitch
name={'continueOnFailure'}
description={'Future tasks will be run when this task fails.'}
Expand Down