Add underlying data changes necessary for new task & schedule features
This commit is contained in:
parent
cf1ac04e39
commit
92cd659db3
13 changed files with 201 additions and 107 deletions
|
@ -16,7 +16,6 @@ use Pterodactyl\Services\Schedules\ProcessScheduleService;
|
|||
use Pterodactyl\Transformers\Api\Client\ScheduleTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreScheduleRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\DeleteScheduleRequest;
|
||||
|
@ -81,6 +80,7 @@ class ScheduleController extends ClientApiController
|
|||
'cron_hour' => $request->input('hour'),
|
||||
'cron_minute' => $request->input('minute'),
|
||||
'is_active' => (bool) $request->input('is_active'),
|
||||
'only_when_online' => (bool) $request->input('only_when_online'),
|
||||
'next_run_at' => $this->getNextRunAt($request),
|
||||
]);
|
||||
|
||||
|
@ -128,6 +128,7 @@ class ScheduleController extends ClientApiController
|
|||
'cron_hour' => $request->input('hour'),
|
||||
'cron_minute' => $request->input('minute'),
|
||||
'is_active' => $active,
|
||||
'only_when_online' => (bool) $request->input('only_when_online'),
|
||||
'next_run_at' => $this->getNextRunAt($request),
|
||||
];
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ class ScheduleTaskController extends ClientApiController
|
|||
'action' => $request->input('action'),
|
||||
'payload' => $request->input('payload') ?? '',
|
||||
'time_offset' => $request->input('time_offset'),
|
||||
'continue_on_failure' => (bool) $request->input('continue_on_failure'),
|
||||
]);
|
||||
|
||||
return $this->fractal->item($task)
|
||||
|
@ -84,6 +85,7 @@ class ScheduleTaskController extends ClientApiController
|
|||
'action' => $request->input('action'),
|
||||
'payload' => $request->input('payload') ?? '',
|
||||
'time_offset' => $request->input('time_offset'),
|
||||
'continue_on_failure' => (bool) $request->input('continue_on_failure'),
|
||||
]);
|
||||
|
||||
return $this->fractal->item($task->refresh())
|
||||
|
|
|
@ -7,6 +7,7 @@ use Pterodactyl\Jobs\Job;
|
|||
use Carbon\CarbonImmutable;
|
||||
use Pterodactyl\Models\Task;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Http\Response;
|
||||
use Pterodactyl\Models\Schedule;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
@ -15,6 +16,7 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
|
|||
use Pterodactyl\Services\Backups\InitiateBackupService;
|
||||
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
|
||||
class RunTaskJob extends Job implements ShouldQueue
|
||||
{
|
||||
|
@ -62,18 +64,32 @@ class RunTaskJob extends Job implements ShouldQueue
|
|||
|
||||
$server = $this->task->server;
|
||||
// Perform the provided task against the daemon.
|
||||
switch ($this->task->action) {
|
||||
case 'power':
|
||||
$powerRepository->setServer($server)->send($this->task->payload);
|
||||
break;
|
||||
case 'command':
|
||||
$commandRepository->setServer($server)->send($this->task->payload);
|
||||
break;
|
||||
case 'backup':
|
||||
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.');
|
||||
try {
|
||||
switch ($this->task->action) {
|
||||
case Task::ACTION_POWER:
|
||||
$powerRepository->setServer($server)->send($this->task->payload);
|
||||
break;
|
||||
case Task::ACTION_COMMAND:
|
||||
$commandRepository->setServer($server)->send($this->task->payload);
|
||||
break;
|
||||
case Task::ACTION_BACKUP:
|
||||
$backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.');
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
if ($exception instanceof DaemonConnectionException) {
|
||||
// If the task "failed" because the server is offline and it was sending a command or
|
||||
// executing a power action (which shouldn't happen?) then just stop trying to process
|
||||
// the schedule, but don't actually log the failure.
|
||||
if ($this->task->action === Task::ACTION_POWER || $this->task->action === Task::ACTION_COMMAND) {
|
||||
// Do the thing
|
||||
if ($exception->getStatusCode() === Response::HTTP_CONFLICT) {
|
||||
}
|
||||
}
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->markTaskNotQueued();
|
||||
|
|
|
@ -18,6 +18,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
|||
* @property string $cron_minute
|
||||
* @property bool $is_active
|
||||
* @property bool $is_processing
|
||||
* @property bool $only_when_online
|
||||
* @property \Carbon\Carbon|null $last_run_at
|
||||
* @property \Carbon\Carbon|null $next_run_at
|
||||
* @property \Carbon\Carbon $created_at
|
||||
|
@ -63,6 +64,7 @@ class Schedule extends Model
|
|||
'cron_minute',
|
||||
'is_active',
|
||||
'is_processing',
|
||||
'only_when_online',
|
||||
'last_run_at',
|
||||
'next_run_at',
|
||||
];
|
||||
|
@ -75,6 +77,7 @@ class Schedule extends Model
|
|||
'server_id' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'is_processing' => 'boolean',
|
||||
'only_when_online' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -99,6 +102,7 @@ class Schedule extends Model
|
|||
'cron_minute' => '*',
|
||||
'is_active' => true,
|
||||
'is_processing' => false,
|
||||
'only_when_online' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -114,6 +118,7 @@ class Schedule extends Model
|
|||
'cron_minute' => 'required|string',
|
||||
'is_active' => 'boolean',
|
||||
'is_processing' => 'boolean',
|
||||
'only_when_online' => 'boolean',
|
||||
'last_run_at' => 'nullable|date',
|
||||
'next_run_at' => 'nullable|date',
|
||||
];
|
||||
|
@ -122,6 +127,7 @@ class Schedule extends Model
|
|||
* Returns the schedule's execution crontab entry as a string.
|
||||
*
|
||||
* @return \Carbon\CarbonImmutable
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getNextRunDate()
|
||||
|
|
|
@ -14,6 +14,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
|||
* @property string $payload
|
||||
* @property int $time_offset
|
||||
* @property bool $is_queued
|
||||
* @property bool $continue_on_failure
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property string $hashid
|
||||
|
@ -30,6 +31,13 @@ class Task extends Model
|
|||
*/
|
||||
public const RESOURCE_NAME = 'schedule_task';
|
||||
|
||||
/**
|
||||
* The default actions that can exist for a task in Pterodactyl.
|
||||
*/
|
||||
public const ACTION_POWER = 'power';
|
||||
public const ACTION_COMMAND = 'command';
|
||||
public const ACTION_BACKUP = 'backup';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
|
@ -56,6 +64,7 @@ class Task extends Model
|
|||
'payload',
|
||||
'time_offset',
|
||||
'is_queued',
|
||||
'continue_on_failure',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -69,6 +78,7 @@ class Task extends Model
|
|||
'sequence_id' => 'integer',
|
||||
'time_offset' => 'integer',
|
||||
'is_queued' => 'boolean',
|
||||
'continue_on_failure' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -79,6 +89,7 @@ class Task extends Model
|
|||
protected $attributes = [
|
||||
'time_offset' => 0,
|
||||
'is_queued' => false,
|
||||
'continue_on_failure' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -91,6 +102,7 @@ class Task extends Model
|
|||
'payload' => 'required_unless:action,backup|string',
|
||||
'time_offset' => 'required|numeric|between:0,900',
|
||||
'is_queued' => 'boolean',
|
||||
'continue_on_failure' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,6 +45,7 @@ class ScheduleTransformer extends BaseClientTransformer
|
|||
],
|
||||
'is_active' => $model->is_active,
|
||||
'is_processing' => $model->is_processing,
|
||||
'only_when_online' => $model->only_when_online,
|
||||
'last_run_at' => $model->last_run_at ? $model->last_run_at->toIso8601String() : null,
|
||||
'next_run_at' => $model->next_run_at ? $model->next_run_at->toIso8601String() : null,
|
||||
'created_at' => $model->created_at->toIso8601String(),
|
||||
|
|
|
@ -28,6 +28,7 @@ class TaskTransformer extends BaseClientTransformer
|
|||
'payload' => $model->payload,
|
||||
'time_offset' => $model->time_offset,
|
||||
'is_queued' => $model->is_queued,
|
||||
'continue_on_failure' => $model->continue_on_failure,
|
||||
'created_at' => $model->created_at->toIso8601String(),
|
||||
'updated_at' => $model->updated_at->toIso8601String(),
|
||||
];
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddContinueOnFailureOptionToTasks extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('continue_on_failure')->after('is_queued')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->dropColumn('continue_on_failure');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddOnlyRunWhenServerOnlineOptionToSchedules extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('schedules', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('only_when_online')->after('is_processing')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('schedules', function (Blueprint $table) {
|
||||
$table->dropColumn('only_when_online');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,20 +1,19 @@
|
|||
import { rawDataToServerSchedule, Schedule } from '@/api/server/schedules/getServerSchedules';
|
||||
import http from '@/api/http';
|
||||
|
||||
type Data = Pick<Schedule, 'cron' | 'name' | 'isActive'> & { id?: number }
|
||||
type Data = Pick<Schedule, 'cron' | 'name' | 'onlyWhenOnline' | 'isActive'> & { id?: number }
|
||||
|
||||
export default (uuid: string, schedule: Data): Promise<Schedule> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post(`/api/client/servers/${uuid}/schedules${schedule.id ? `/${schedule.id}` : ''}`, {
|
||||
is_active: schedule.isActive,
|
||||
name: schedule.name,
|
||||
minute: schedule.cron.minute,
|
||||
hour: schedule.cron.hour,
|
||||
day_of_month: schedule.cron.dayOfMonth,
|
||||
month: schedule.cron.month,
|
||||
day_of_week: schedule.cron.dayOfWeek,
|
||||
})
|
||||
.then(({ data }) => resolve(rawDataToServerSchedule(data.attributes)))
|
||||
.catch(reject);
|
||||
export default async (uuid: string, schedule: Data): Promise<Schedule> => {
|
||||
const { data } = await http.post(`/api/client/servers/${uuid}/schedules${schedule.id ? `/${schedule.id}` : ''}`, {
|
||||
is_active: schedule.isActive,
|
||||
only_when_online: schedule.onlyWhenOnline,
|
||||
name: schedule.name,
|
||||
minute: schedule.cron.minute,
|
||||
hour: schedule.cron.hour,
|
||||
day_of_month: schedule.cron.dayOfMonth,
|
||||
month: schedule.cron.month,
|
||||
day_of_week: schedule.cron.dayOfWeek,
|
||||
});
|
||||
|
||||
return rawDataToServerSchedule(data.attributes);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface Schedule {
|
|||
};
|
||||
isActive: boolean;
|
||||
isProcessing: boolean;
|
||||
onlyWhenOnline: boolean;
|
||||
lastRunAt: Date | null;
|
||||
nextRunAt: Date | null;
|
||||
createdAt: Date;
|
||||
|
@ -27,6 +28,7 @@ export interface Task {
|
|||
payload: string;
|
||||
timeOffset: number;
|
||||
isQueued: boolean;
|
||||
continueOnFailure: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
@ -38,6 +40,7 @@ export const rawDataToServerTask = (data: any): Task => ({
|
|||
payload: data.payload,
|
||||
timeOffset: data.time_offset,
|
||||
isQueued: data.is_queued,
|
||||
continueOnFailure: data.continue_on_failure,
|
||||
createdAt: new Date(data.created_at),
|
||||
updatedAt: new Date(data.updated_at),
|
||||
});
|
||||
|
@ -54,6 +57,7 @@ export const rawDataToServerSchedule = (data: any): Schedule => ({
|
|||
},
|
||||
isActive: data.is_active,
|
||||
isProcessing: data.is_processing,
|
||||
onlyWhenOnline: data.only_when_online,
|
||||
lastRunAt: data.last_run_at ? new Date(data.last_run_at) : null,
|
||||
nextRunAt: data.next_run_at ? new Date(data.next_run_at) : null,
|
||||
createdAt: new Date(data.created_at),
|
||||
|
@ -62,14 +66,12 @@ export const rawDataToServerSchedule = (data: any): Schedule => ({
|
|||
tasks: (data.relationships?.tasks?.data || []).map((row: any) => rawDataToServerTask(row.attributes)),
|
||||
});
|
||||
|
||||
export default (uuid: string): Promise<Schedule[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(`/api/client/servers/${uuid}/schedules`, {
|
||||
params: {
|
||||
include: [ 'tasks' ],
|
||||
},
|
||||
})
|
||||
.then(({ data }) => resolve((data.data || []).map((row: any) => rawDataToServerSchedule(row.attributes))))
|
||||
.catch(reject);
|
||||
export default async (uuid: string): Promise<Schedule[]> => {
|
||||
const { data } = await http.get(`/api/client/servers/${uuid}/schedules`, {
|
||||
params: {
|
||||
include: [ 'tasks' ],
|
||||
},
|
||||
});
|
||||
|
||||
return (data.data || []).map((row: any) => rawDataToServerSchedule(row.attributes));
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ const Field = forwardRef<HTMLInputElement, Props>(({ id, name, light = false, la
|
|||
<FormikField innerRef={ref} name={name} validate={validate}>
|
||||
{
|
||||
({ field, form: { errors, touched } }: FieldProps) => (
|
||||
<>
|
||||
<div>
|
||||
{label &&
|
||||
<Label htmlFor={id} isLight={light}>{label}</Label>
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ const Field = forwardRef<HTMLInputElement, Props>(({ id, name, light = false, la
|
|||
:
|
||||
description ? <p className={'input-help'}>{description}</p> : null
|
||||
}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</FormikField>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Schedule } from '@/api/server/schedules/getServerSchedules';
|
||||
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
||||
import Field from '@/components/elements/Field';
|
||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||
import createOrUpdateSchedule from '@/api/server/schedules/createOrUpdateSchedule';
|
||||
import { ServerContext } from '@/state/server';
|
||||
|
@ -11,10 +10,12 @@ import FlashMessageRender from '@/components/FlashMessageRender';
|
|||
import useFlash from '@/plugins/useFlash';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import ModalContext from '@/context/ModalContext';
|
||||
import asModal from '@/hoc/asModal';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
schedule?: Schedule;
|
||||
} & RequiredModalProps;
|
||||
}
|
||||
|
||||
interface Values {
|
||||
name: string;
|
||||
|
@ -24,70 +25,21 @@ interface Values {
|
|||
hour: string;
|
||||
minute: string;
|
||||
enabled: boolean;
|
||||
onlyWhenOnline: boolean;
|
||||
}
|
||||
|
||||
const EditScheduleModal = ({ schedule, ...props }: Omit<Props, 'onScheduleUpdated'>) => {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
return (
|
||||
<Modal {...props} showSpinnerOverlay={isSubmitting}>
|
||||
<h3 css={tw`text-2xl 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 css={tw`grid grid-cols-2 sm:grid-cols-5 gap-4 mt-6`}>
|
||||
<div>
|
||||
<Field name={'minute'} label={'Minute'}/>
|
||||
</div>
|
||||
<div>
|
||||
<Field name={'hour'} label={'Hour'}/>
|
||||
</div>
|
||||
<div>
|
||||
<Field name={'dayOfMonth'} label={'Day of month'}/>
|
||||
</div>
|
||||
<div>
|
||||
<Field name={'month'} label={'Month'}/>
|
||||
</div>
|
||||
<div>
|
||||
<Field name={'dayOfWeek'} label={'Day of week'}/>
|
||||
</div>
|
||||
</div>
|
||||
<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 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 css={tw`mt-6 text-right`}>
|
||||
<Button css={tw`w-full sm:w-auto`} type={'submit'} disabled={isSubmitting}>
|
||||
{schedule ? 'Save changes' : 'Create schedule'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ({ schedule, visible, ...props }: Props) => {
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||
const EditScheduleModal = ({ schedule }: Props) => {
|
||||
const { addError, clearFlashes } = useFlash();
|
||||
const [ modalVisible, setModalVisible ] = useState(visible);
|
||||
const { dismiss } = useContext(ModalContext);
|
||||
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
|
||||
|
||||
useEffect(() => {
|
||||
setModalVisible(visible);
|
||||
clearFlashes('schedule:edit');
|
||||
}, [ visible ]);
|
||||
return () => {
|
||||
clearFlashes('schedule:edit');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||
clearFlashes('schedule:edit');
|
||||
|
@ -101,12 +53,13 @@ export default ({ schedule, visible, ...props }: Props) => {
|
|||
month: values.month,
|
||||
dayOfMonth: values.dayOfMonth,
|
||||
},
|
||||
onlyWhenOnline: values.onlyWhenOnline,
|
||||
isActive: values.enabled,
|
||||
})
|
||||
.then(schedule => {
|
||||
setSubmitting(false);
|
||||
appendSchedule(schedule);
|
||||
setModalVisible(false);
|
||||
dismiss();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
@ -128,13 +81,50 @@ export default ({ schedule, visible, ...props }: Props) => {
|
|||
dayOfWeek: schedule?.cron.dayOfWeek || '*',
|
||||
enabled: schedule ? schedule.isActive : true,
|
||||
} as Values}
|
||||
validationSchema={null}
|
||||
>
|
||||
<EditScheduleModal
|
||||
visible={modalVisible}
|
||||
schedule={schedule}
|
||||
{...props}
|
||||
/>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<h3 css={tw`text-2xl mb-6`}>{schedule ? 'Edit schedule' : 'Create new schedule'}</h3>
|
||||
<FlashMessageRender byKey={'schedule:edit'} css={tw`mb-6`}/>
|
||||
<Field
|
||||
name={'name'}
|
||||
label={'Schedule name'}
|
||||
description={'A human readable identifer for this schedule.'}
|
||||
/>
|
||||
<div css={tw`grid grid-cols-2 sm:grid-cols-5 gap-4 mt-6`}>
|
||||
<Field name={'minute'} label={'Minute'}/>
|
||||
<Field name={'hour'} label={'Hour'}/>
|
||||
<Field name={'dayOfMonth'} label={'Day of month'}/>
|
||||
<Field name={'month'} label={'Month'}/>
|
||||
<Field name={'dayOfWeek'} label={'Day of week'}/>
|
||||
</div>
|
||||
<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 css={tw`mt-6 bg-neutral-700 border border-neutral-800 shadow-inner p-4 rounded`}>
|
||||
<FormikSwitch
|
||||
name={'only_when_online'}
|
||||
description={'If disabled this schedule will always run, regardless of the server\'s current power state.'}
|
||||
label={'Only When Server Is Online'}
|
||||
/>
|
||||
</div>
|
||||
<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={'Schedule Enabled'}
|
||||
/>
|
||||
</div>
|
||||
<div css={tw`mt-6 text-right`}>
|
||||
<Button css={tw`w-full sm:w-auto`} type={'submit'} disabled={isSubmitting}>
|
||||
{schedule ? 'Save changes' : 'Create schedule'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default asModal<Props>()(EditScheduleModal);
|
||||
|
|
Loading…
Reference in a new issue