From 036bea2b94f63b2074f2ace9291447da4d95c7cb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 8 Jan 2018 21:43:10 -0600 Subject: [PATCH] Update schedule process to allow toggling/triggering via UI --- .../ScheduleRepositoryInterface.php | 10 +++ .../Repository/TaskRepositoryInterface.php | 2 +- .../Server/Tasks/ActionController.php | 70 ++++++++++++++++ .../Server/Tasks/TaskManagementController.php | 2 +- app/Jobs/Schedule/RunTaskJob.php | 27 +++---- .../Eloquent/ScheduleRepository.php | 17 ++++ app/Repositories/Eloquent/TaskRepository.php | 4 +- .../Schedules/ProcessScheduleService.php | 63 ++++++++++----- .../Schedules/ScheduleCreationService.php | 45 ++++------- public/js/laroute.js | 2 +- .../js/frontend/tasks/management-actions.js | 40 ++++++++++ resources/lang/en/server.php | 3 + .../server/schedules/index.blade.php | 24 +++--- routes/server.php | 3 +- .../CleanServiceBackupFilesCommandTest.php | 2 +- tests/Unit/Jobs/Schedule/RunTaskJobTest.php | 46 ++++++++--- .../Schedules/ProcessScheduleServiceTest.php | 61 +++----------- .../Schedules/ScheduleCreationServiceTest.php | 80 ++----------------- 18 files changed, 280 insertions(+), 221 deletions(-) create mode 100644 app/Http/Controllers/Server/Tasks/ActionController.php diff --git a/app/Contracts/Repository/ScheduleRepositoryInterface.php b/app/Contracts/Repository/ScheduleRepositoryInterface.php index 32650bdcf..67bf40271 100644 --- a/app/Contracts/Repository/ScheduleRepositoryInterface.php +++ b/app/Contracts/Repository/ScheduleRepositoryInterface.php @@ -15,6 +15,16 @@ interface ScheduleRepositoryInterface extends RepositoryInterface */ public function findServerSchedules(int $server): Collection; + /** + * Load the tasks relationship onto the Schedule module if they are not + * already present. + * + * @param \Pterodactyl\Models\Schedule $schedule + * @param bool $refresh + * @return \Pterodactyl\Models\Schedule + */ + public function loadTasks(Schedule $schedule, bool $refresh = false): Schedule; + /** * Return a schedule model with all of the associated tasks as a relationship. * diff --git a/app/Contracts/Repository/TaskRepositoryInterface.php b/app/Contracts/Repository/TaskRepositoryInterface.php index 80ca277bb..11cb704e1 100644 --- a/app/Contracts/Repository/TaskRepositoryInterface.php +++ b/app/Contracts/Repository/TaskRepositoryInterface.php @@ -14,7 +14,7 @@ interface TaskRepositoryInterface extends RepositoryInterface * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getTaskWithServer(int $id): Task; + public function getTaskForJobProcess(int $id): Task; /** * Returns the next task in a schedule. diff --git a/app/Http/Controllers/Server/Tasks/ActionController.php b/app/Http/Controllers/Server/Tasks/ActionController.php new file mode 100644 index 000000000..410d7c189 --- /dev/null +++ b/app/Http/Controllers/Server/Tasks/ActionController.php @@ -0,0 +1,70 @@ +processScheduleService = $processScheduleService; + $this->repository = $repository; + } + + /** + * Toggle a task to be active or inactive for a given server. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function toggle(Request $request): Response + { + $server = $request->attributes->get('server'); + $schedule = $request->attributes->get('schedule'); + $this->authorize('toggle-schedule', $server); + + $this->repository->update($schedule->id, [ + 'is_active' => ! $schedule->is_active, + ]); + + return response('', 204); + } + + /** + * Trigger a schedule to run now. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + * + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function trigger(Request $request): Response + { + $server = $request->attributes->get('server'); + $this->authorize('toggle-schedule', $server); + + $this->processScheduleService->setRunTimeOverride(Carbon::now())->handle( + $request->attributes->get('schedule') + ); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php index 9e6782e93..238846258 100644 --- a/app/Http/Controllers/Server/Tasks/TaskManagementController.php +++ b/app/Http/Controllers/Server/Tasks/TaskManagementController.php @@ -112,7 +112,7 @@ class TaskManagementController extends Controller $server = $request->attributes->get('server'); $schedule = $this->creationService->handle($server, $request->normalize(), $request->getTasks()); - $this->alert->success(trans('server.schedules.task_created'))->flash(); + $this->alert->success(trans('server.schedule.task_created'))->flash(); return redirect()->route('server.schedules.view', [ 'server' => $server->uuidShort, diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 021fa496b..a00052b1a 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -1,18 +1,10 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Jobs\Schedule; use Exception; use Carbon\Carbon; use Pterodactyl\Jobs\Job; -use Webmozart\Assert\Assert; use InvalidArgumentException; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; @@ -59,12 +51,9 @@ class RunTaskJob extends Job implements ShouldQueue * @param int $task * @param int $schedule */ - public function __construct($task, $schedule) + public function __construct(int $task, int $schedule) { - Assert::integerish($task, 'First argument passed to constructor must be integer, received %s.'); - Assert::integerish($schedule, 'Second argument passed to constructor must be integer, received %s.'); - - $this->queue = app()->make('config')->get('pterodactyl.queues.standard'); + $this->queue = config('pterodactyl.queues.standard'); $this->task = $task; $this->schedule = $schedule; } @@ -91,10 +80,18 @@ class RunTaskJob extends Job implements ShouldQueue $this->powerRepository = $powerRepository; $this->taskRepository = $taskRepository; - $task = $this->taskRepository->getTaskWithServer($this->task); + $task = $this->taskRepository->getTaskForJobProcess($this->task); $server = $task->getRelation('server'); $user = $server->getRelation('user'); + // Do not process a task that is not set to active. + if (! $task->getRelation('schedule')->is_active) { + $this->markTaskNotQueued(); + $this->markScheduleComplete(); + + return; + } + // Perform the provided task aganist the daemon. switch ($task->action) { case 'power': @@ -108,7 +105,7 @@ class RunTaskJob extends Job implements ShouldQueue ->send($task->payload); break; default: - throw new InvalidArgumentException('Cannot run a task that points to a non-existant action.'); + throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); } $this->markTaskNotQueued(); diff --git a/app/Repositories/Eloquent/ScheduleRepository.php b/app/Repositories/Eloquent/ScheduleRepository.php index 02be96439..a7b53602b 100644 --- a/app/Repositories/Eloquent/ScheduleRepository.php +++ b/app/Repositories/Eloquent/ScheduleRepository.php @@ -31,6 +31,23 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor return $this->getBuilder()->withCount('tasks')->where('server_id', '=', $server)->get($this->getColumns()); } + /** + * Load the tasks relationship onto the Schedule module if they are not + * already present. + * + * @param \Pterodactyl\Models\Schedule $schedule + * @param bool $refresh + * @return \Pterodactyl\Models\Schedule + */ + public function loadTasks(Schedule $schedule, bool $refresh = false): Schedule + { + if (! $schedule->relationLoaded('tasks') || $refresh) { + $schedule->load('tasks'); + } + + return $schedule; + } + /** * Return a schedule model with all of the associated tasks as a relationship. * diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php index ef4a92a0b..0c1202f59 100644 --- a/app/Repositories/Eloquent/TaskRepository.php +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -27,10 +27,10 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function getTaskWithServer(int $id): Task + public function getTaskForJobProcess(int $id): Task { try { - return $this->getBuilder()->with('server.user')->findOrFail($id, $this->getColumns()); + return $this->getBuilder()->with('server.user', 'schedule')->findOrFail($id, $this->getColumns()); } catch (ModelNotFoundException $exception) { throw new RecordNotFoundException; } diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index f5420b3a3..ec6ea5f12 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -1,16 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Schedules; +use Carbon\Carbon; use Cron\CronExpression; -use Webmozart\Assert\Assert; use Pterodactyl\Models\Schedule; use Pterodactyl\Services\Schedules\Tasks\RunTaskService; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; @@ -20,12 +13,17 @@ class ProcessScheduleService /** * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface */ - protected $repository; + private $repository; /** * @var \Pterodactyl\Services\Schedules\Tasks\RunTaskService */ - protected $runnerService; + private $runnerService; + + /** + * @var \Carbon\Carbon|null + */ + private $runTimeOverride; /** * ProcessScheduleService constructor. @@ -39,23 +37,31 @@ class ProcessScheduleService $this->runnerService = $runnerService; } + /** + * Set the time that this schedule should be run at. This will override the time + * defined on the schedule itself. Useful for triggering one-off task runs. + * + * @param \Carbon\Carbon $time + * @return $this + */ + public function setRunTimeOverride(Carbon $time) + { + $this->runTimeOverride = $time; + + return $this; + } + /** * Process a schedule and push the first task onto the queue worker. * - * @param int|\Pterodactyl\Models\Schedule $schedule + * @param \Pterodactyl\Models\Schedule $schedule * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($schedule) + public function handle(Schedule $schedule) { - Assert::true(($schedule instanceof Schedule || is_digit($schedule)), - 'First argument passed to handle must be instance of \Pterodactyl\Models\Schedule or an integer, received %s.' - ); - - if (($schedule instanceof Schedule && ! $schedule->relationLoaded('tasks')) || ! $schedule instanceof Schedule) { - $schedule = $this->repository->getScheduleWithTasks(is_digit($schedule) ? $schedule : $schedule->id); - } + $this->repository->loadTasks($schedule); $formattedCron = sprintf('%s %s %s * %s *', $schedule->cron_minute, @@ -66,10 +72,25 @@ class ProcessScheduleService $this->repository->update($schedule->id, [ 'is_processing' => true, - 'next_run_at' => CronExpression::factory($formattedCron)->getNextRunDate(), + 'next_run_at' => $this->getRunAtTime($formattedCron), ]); - $task = $schedule->tasks->where('sequence_id', 1)->first(); + $task = $schedule->getRelation('tasks')->where('sequence_id', 1)->first(); $this->runnerService->handle($task); } + + /** + * Get the timestamp to store in the database as the next_run time for a schedule. + * + * @param string $formatted + * @return \DateTime|string + */ + private function getRunAtTime(string $formatted) + { + if ($this->runTimeOverride instanceof Carbon) { + return $this->runTimeOverride->toDateTimeString(); + } + + return CronExpression::factory($formatted)->getNextRunDate(); + } } diff --git a/app/Services/Schedules/ScheduleCreationService.php b/app/Services/Schedules/ScheduleCreationService.php index 5dbe9e9f3..9f676e246 100644 --- a/app/Services/Schedules/ScheduleCreationService.php +++ b/app/Services/Schedules/ScheduleCreationService.php @@ -1,16 +1,8 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Services\Schedules; use Cron\CronExpression; -use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Schedules\Tasks\TaskCreationService; @@ -53,37 +45,32 @@ class ScheduleCreationService /** * Create a new schedule for a specific server. * - * @param int|\Pterodactyl\Models\Server $server - * @param array $data - * @param array $tasks + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @param array $tasks * @return \Pterodactyl\Models\Schedule * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Schedule\Task\TaskIntervalTooLongException */ - public function handle($server, array $data, array $tasks = []) + public function handle(Server $server, array $data, array $tasks = []) { - Assert::true(($server instanceof Server || is_digit($server)), - 'First argument passed to handle must be numeric or instance of \Pterodactyl\Models\Server, received %s.' - ); - - $server = ($server instanceof Server) ? $server->id : $server; - $data['server_id'] = $server; - $data['next_run_at'] = $this->getCronTimestamp($data); + $data = array_merge($data, [ + 'server_id' => $server->id, + 'next_run_at' => $this->getCronTimestamp($data), + ]); $this->connection->beginTransaction(); $schedule = $this->repository->create($data); - if (! empty($tasks)) { - foreach ($tasks as $index => $task) { - $this->taskCreationService->handle($schedule, [ - 'time_interval' => array_get($task, 'time_interval'), - 'time_value' => array_get($task, 'time_value'), - 'sequence_id' => $index + 1, - 'action' => array_get($task, 'action'), - 'payload' => array_get($task, 'payload'), - ], false); - } + foreach ($tasks as $index => $task) { + $this->taskCreationService->handle($schedule, [ + 'time_interval' => array_get($task, 'time_interval'), + 'time_value' => array_get($task, 'time_value'), + 'sequence_id' => $index + 1, + 'action' => array_get($task, 'action'), + 'payload' => array_get($task, 'payload'), + ], false); } $this->connection->commit(); diff --git a/public/js/laroute.js b/public/js/laroute.js index 3c0ad073f..ea2d1a03d 100644 --- a/public/js/laroute.js +++ b/public/js/laroute.js @@ -6,7 +6,7 @@ absolute: false, rootUrl: 'http://pterodactyl.app', - routes : [{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\BaseController@getSettings"},{"host":null,"methods":["POST"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\BaseController@postSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new\/nodes","name":"admin.servers.new.nodes","action":"Pterodactyl\Http\Controllers\Admin\ServersController@nodes"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details\/container","name":"admin.servers.view.details.container","action":"Pterodactyl\Http\Controllers\Admin\ServersController@setContainer"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@totpCheckpoint"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\Settings\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskToggleController@index"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["POST"],"uri":"api\/remote\/sftp","name":"api.remote.sftp","action":"Pterodactyl\Http\Controllers\API\Remote\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/details\/option\/{server}","name":"daemon.option.details","action":"Pterodactyl\Http\Controllers\Daemon\OptionController@details"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"}], + routes : [{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/open","name":"debugbar.openhandler","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@handle"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/clockwork\/{id}","name":"debugbar.clockwork","action":"Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/stylesheets","name":"debugbar.assets.css","action":"Barryvdh\Debugbar\Controllers\AssetController@css"},{"host":null,"methods":["GET","HEAD"],"uri":"_debugbar\/assets\/javascript","name":"debugbar.assets.js","action":"Barryvdh\Debugbar\Controllers\AssetController@js"},{"host":null,"methods":["GET","HEAD"],"uri":"\/","name":"index","action":"Pterodactyl\Http\Controllers\Base\IndexController@getIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"status\/{server}","name":"index.status","action":"Pterodactyl\Http\Controllers\Base\IndexController@status"},{"host":null,"methods":["GET","HEAD"],"uri":"account","name":"account","action":"Pterodactyl\Http\Controllers\Base\AccountController@index"},{"host":null,"methods":["POST"],"uri":"account","name":null,"action":"Pterodactyl\Http\Controllers\Base\AccountController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api","name":"account.api","action":"Pterodactyl\Http\Controllers\Base\APIController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/api\/new","name":"account.api.new","action":"Pterodactyl\Http\Controllers\Base\APIController@create"},{"host":null,"methods":["POST"],"uri":"account\/api\/new","name":null,"action":"Pterodactyl\Http\Controllers\Base\APIController@store"},{"host":null,"methods":["DELETE"],"uri":"account\/api\/revoke\/{key}","name":"account.api.revoke","action":"Pterodactyl\Http\Controllers\Base\APIController@revoke"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security","name":"account.security","action":"Pterodactyl\Http\Controllers\Base\SecurityController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"account\/security\/revoke\/{id}","name":"account.security.revoke","action":"Pterodactyl\Http\Controllers\Base\SecurityController@revoke"},{"host":null,"methods":["PUT"],"uri":"account\/security\/totp","name":"account.security.totp","action":"Pterodactyl\Http\Controllers\Base\SecurityController@generateTotp"},{"host":null,"methods":["POST"],"uri":"account\/security\/totp","name":"account.security.totp.set","action":"Pterodactyl\Http\Controllers\Base\SecurityController@setTotp"},{"host":null,"methods":["DELETE"],"uri":"account\/security\/totp","name":"account.security.totp.disable","action":"Pterodactyl\Http\Controllers\Base\SecurityController@disableTotp"},{"host":null,"methods":["GET","HEAD"],"uri":"admin","name":"admin.index","action":"Pterodactyl\Http\Controllers\Admin\BaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations","name":"admin.locations","action":"Pterodactyl\Http\Controllers\Admin\LocationController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/locations\/view\/{location}","name":"admin.locations.view","action":"Pterodactyl\Http\Controllers\Admin\LocationController@view"},{"host":null,"methods":["POST"],"uri":"admin\/locations","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/locations\/view\/{location}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\LocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases","name":"admin.databases","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/databases\/view\/{host}","name":"admin.databases.view","action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@view"},{"host":null,"methods":["POST"],"uri":"admin\/databases","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@create"},{"host":null,"methods":["PATCH"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/databases\/view\/{host}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\DatabaseController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings","name":"admin.settings","action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/mail","name":"admin.settings.mail","action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/settings\/advanced","name":"admin.settings.advanced","action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@index"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\IndexController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/mail","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\MailController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/settings\/advanced","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Settings\AdvancedController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users","name":"admin.users","action":"Pterodactyl\Http\Controllers\Admin\UserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/accounts.json","name":"admin.users.json","action":"Pterodactyl\Http\Controllers\Admin\UserController@json"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/new","name":"admin.users.new","action":"Pterodactyl\Http\Controllers\Admin\UserController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/users\/view\/{user}","name":"admin.users.view","action":"Pterodactyl\Http\Controllers\Admin\UserController@view"},{"host":null,"methods":["POST"],"uri":"admin\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@store"},{"host":null,"methods":["PATCH"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/users\/view\/{user}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\UserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers","name":"admin.servers","action":"Pterodactyl\Http\Controllers\Admin\ServersController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/new","name":"admin.servers.new","action":"Pterodactyl\Http\Controllers\Admin\ServersController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}","name":"admin.servers.view","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/details","name":"admin.servers.view.details","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDetails"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/build","name":"admin.servers.view.build","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewBuild"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/startup","name":"admin.servers.view.startup","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewStartup"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/database","name":"admin.servers.view.database","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/manage","name":"admin.servers.view.manage","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewManage"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/servers\/view\/{server}\/delete","name":"admin.servers.view.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@viewDelete"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@store"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/build","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@updateBuild"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@saveStartup"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@newDatabase"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/toggle","name":"admin.servers.view.manage.toggle","action":"Pterodactyl\Http\Controllers\Admin\ServersController@toggleInstall"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/rebuild","name":"admin.servers.view.manage.rebuild","action":"Pterodactyl\Http\Controllers\Admin\ServersController@rebuildContainer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/suspension","name":"admin.servers.view.manage.suspension","action":"Pterodactyl\Http\Controllers\Admin\ServersController@manageSuspension"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/manage\/reinstall","name":"admin.servers.view.manage.reinstall","action":"Pterodactyl\Http\Controllers\Admin\ServersController@reinstallServer"},{"host":null,"methods":["POST"],"uri":"admin\/servers\/view\/{server}\/delete","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@delete"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/details","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@setDetails"},{"host":null,"methods":["PATCH"],"uri":"admin\/servers\/view\/{server}\/database","name":null,"action":"Pterodactyl\Http\Controllers\Admin\ServersController@resetDatabasePassword"},{"host":null,"methods":["DELETE"],"uri":"admin\/servers\/view\/{server}\/database\/{database}\/delete","name":"admin.servers.view.database.delete","action":"Pterodactyl\Http\Controllers\Admin\ServersController@deleteDatabase"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes","name":"admin.nodes","action":"Pterodactyl\Http\Controllers\Admin\NodesController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/new","name":"admin.nodes.new","action":"Pterodactyl\Http\Controllers\Admin\NodesController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}","name":"admin.nodes.view","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewIndex"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings","name":"admin.nodes.view.settings","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewSettings"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/configuration","name":"admin.nodes.view.configuration","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewConfiguration"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":"admin.nodes.view.allocation","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewAllocation"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/servers","name":"admin.nodes.view.servers","action":"Pterodactyl\Http\Controllers\Admin\NodesController@viewServers"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nodes\/view\/{node}\/settings\/token","name":"admin.nodes.view.configuration.token","action":"Pterodactyl\Http\Controllers\Admin\NodesController@setToken"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@createAllocation"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove","name":"admin.nodes.view.allocation.removeBlock","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveBlock"},{"host":null,"methods":["POST"],"uri":"admin\/nodes\/view\/{node}\/allocation\/alias","name":"admin.nodes.view.allocation.setAlias","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationSetAlias"},{"host":null,"methods":["PATCH"],"uri":"admin\/nodes\/view\/{node}\/settings","name":null,"action":"Pterodactyl\Http\Controllers\Admin\NodesController@updateSettings"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/delete","name":"admin.nodes.view.delete","action":"Pterodactyl\Http\Controllers\Admin\NodesController@delete"},{"host":null,"methods":["DELETE"],"uri":"admin\/nodes\/view\/{node}\/allocation\/remove\/{allocation}","name":"admin.nodes.view.allocation.removeSingle","action":"Pterodactyl\Http\Controllers\Admin\NodesController@allocationRemoveSingle"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests","name":"admin.nests","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/new","name":"admin.nests.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/view\/{nest}","name":"admin.nests.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/new","name":"admin.nests.egg.new","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}","name":"admin.nests.egg.view","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/export","name":"admin.nests.egg.export","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@export"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":"admin.nests.egg.variables","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":"admin.nests.egg.scripts","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@index"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/import","name":"admin.nests.egg.import","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@import"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@store"},{"host":null,"methods":["POST"],"uri":"admin\/nests\/egg\/{egg}\/variables","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@store"},{"host":null,"methods":["PUT"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggShareController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/scripts","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggScriptController@update"},{"host":null,"methods":["PATCH"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":"admin.nests.egg.variables.edit","action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/view\/{nest}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\NestController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggController@destroy"},{"host":null,"methods":["DELETE"],"uri":"admin\/nests\/egg\/{egg}\/variables\/{variable}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\Nests\EggVariableController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs","name":"admin.packs","action":"Pterodactyl\Http\Controllers\Admin\PackController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new","name":"admin.packs.new","action":"Pterodactyl\Http\Controllers\Admin\PackController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/new\/template","name":"admin.packs.new.template","action":"Pterodactyl\Http\Controllers\Admin\PackController@newTemplate"},{"host":null,"methods":["GET","HEAD"],"uri":"admin\/packs\/view\/{pack}","name":"admin.packs.view","action":"Pterodactyl\Http\Controllers\Admin\PackController@view"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/new","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@store"},{"host":null,"methods":["POST"],"uri":"admin\/packs\/view\/{pack}\/export\/{files?}","name":"admin.packs.view.export","action":"Pterodactyl\Http\Controllers\Admin\PackController@export"},{"host":null,"methods":["PATCH"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@update"},{"host":null,"methods":["DELETE"],"uri":"admin\/packs\/view\/{pack}","name":null,"action":"Pterodactyl\Http\Controllers\Admin\PackController@destroy"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login","name":"auth.login","action":"Pterodactyl\Http\Controllers\Auth\LoginController@showLoginForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/login\/totp","name":"auth.totp","action":"Pterodactyl\Http\Controllers\Auth\LoginController@totp"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password","name":"auth.password","action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/password\/reset\/{token}","name":"auth.reset","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@showResetForm"},{"host":null,"methods":["POST"],"uri":"auth\/login","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@login"},{"host":null,"methods":["POST"],"uri":"auth\/login\/totp","name":null,"action":"Pterodactyl\Http\Controllers\Auth\LoginController@loginUsingTotp"},{"host":null,"methods":["POST"],"uri":"auth\/password","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset","name":"auth.reset.post","action":"Pterodactyl\Http\Controllers\Auth\ResetPasswordController@reset"},{"host":null,"methods":["POST"],"uri":"auth\/password\/reset\/{token}","name":null,"action":"Pterodactyl\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail"},{"host":null,"methods":["GET","HEAD"],"uri":"auth\/logout","name":"auth.logout","action":"Pterodactyl\Http\Controllers\Auth\LoginController@logout"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}","name":"server.index","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/console","name":"server.console","action":"Pterodactyl\Http\Controllers\Server\ConsoleController@console"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/allocation","name":"server.settings.allocation","action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/allocation","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\AllocationController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/sftp","name":"server.settings.sftp","action":"Pterodactyl\Http\Controllers\Server\Settings\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/settings\/startup","name":"server.settings.startup","action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/settings\/startup","name":null,"action":"Pterodactyl\Http\Controllers\Server\Settings\StartupController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/databases","name":"server.databases.index","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@index"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/databases\/password","name":"server.databases.password","action":"Pterodactyl\Http\Controllers\Server\DatabaseController@update"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files","name":"server.files.index","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/add","name":"server.files.add","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@create"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/edit\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\FileActionsController@view"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/files\/download\/{file}","name":"server.files.edit","action":"Pterodactyl\Http\Controllers\Server\Files\DownloadController@index"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/directory-list","name":"server.files.directory-list","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@directory"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/files\/save","name":"server.files.save","action":"Pterodactyl\Http\Controllers\Server\Files\RemoteRequestController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users","name":"server.subusers","action":"Pterodactyl\Http\Controllers\Server\SubuserController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/new","name":"server.subusers.new","action":"Pterodactyl\Http\Controllers\Server\SubuserController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/users\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/users\/view\/{subuser}","name":"server.subusers.view","action":"Pterodactyl\Http\Controllers\Server\SubuserController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@update"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/users\/view\/{subuser}","name":null,"action":"Pterodactyl\Http\Controllers\Server\SubuserController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules","name":"server.schedules","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/new","name":"server.schedules.new","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@create"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/new","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@store"},{"host":null,"methods":["GET","HEAD"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":"server.schedules.view","action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@view"},{"host":null,"methods":["PATCH"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@update"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/toggle","name":"server.schedules.toggle","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@toggle"},{"host":null,"methods":["POST"],"uri":"server\/{server}\/schedules\/view\/{schedule}\/trigger","name":"server.schedules.trigger","action":"Pterodactyl\Http\Controllers\Server\Tasks\ActionController@trigger"},{"host":null,"methods":["DELETE"],"uri":"server\/{server}\/schedules\/view\/{schedule}","name":null,"action":"Pterodactyl\Http\Controllers\Server\Tasks\TaskManagementController@delete"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/authenticate\/{token}","name":"api.remote.authenticate","action":"Pterodactyl\Http\Controllers\API\Remote\ValidateKeyController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs","name":"api.remote.eggs","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/eggs\/{uuid}","name":"api.remote.eggs.download","action":"Pterodactyl\Http\Controllers\API\Remote\EggRetrievalController@download"},{"host":null,"methods":["GET","HEAD"],"uri":"api\/remote\/scripts\/{uuid}","name":"api.remote.scripts","action":"Pterodactyl\Http\Controllers\API\Remote\EggInstallController@index"},{"host":null,"methods":["POST"],"uri":"api\/remote\/sftp","name":"api.remote.sftp","action":"Pterodactyl\Http\Controllers\API\Remote\SftpController@index"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}","name":"daemon.pack.pull","action":"Pterodactyl\Http\Controllers\Daemon\PackController@pull"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/packs\/pull\/{uuid}\/hash","name":"daemon.pack.hash","action":"Pterodactyl\Http\Controllers\Daemon\PackController@hash"},{"host":null,"methods":["GET","HEAD"],"uri":"daemon\/configure\/{token}","name":"daemon.configuration","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@configuration"},{"host":null,"methods":["POST"],"uri":"daemon\/download","name":"daemon.download","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@authenticateDownload"},{"host":null,"methods":["POST"],"uri":"daemon\/install","name":"daemon.install","action":"Pterodactyl\Http\Controllers\Daemon\ActionController@markInstall"}], prefix: '', route : function (name, parameters, route) { diff --git a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js index 60a8194bf..3c344a971 100644 --- a/public/themes/pterodactyl/js/frontend/tasks/management-actions.js +++ b/public/themes/pterodactyl/js/frontend/tasks/management-actions.js @@ -19,6 +19,7 @@ // SOFTWARE. $(document).ready(function () { + $('[data-toggle="tooltip"]').tooltip(); $('[data-action="delete-schedule"]').click(function () { var self = $(this); swal({ @@ -59,6 +60,45 @@ $(document).ready(function () { }); }); + $('[data-action="trigger-schedule"]').click(function (event) { + event.preventDefault(); + var self = $(this); + swal({ + type: 'info', + title: 'Trigger Schedule', + text: 'This will run the selected schedule now.', + showCancelButton: true, + allowOutsideClick: true, + closeOnConfirm: false, + confirmButtonText: 'Continue', + showLoaderOnConfirm: true + }, function () { + $.ajax({ + method: 'POST', + url: Router.route('server.schedules.trigger', { + server: Pterodactyl.server.uuidShort, + schedule: self.data('schedule-id'), + }), + headers: { + 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'), + }, + }).done(function (data) { + swal({ + type: 'success', + title: '', + text: 'Schedule has been added to the next-run queue.' + }); + }).fail(function (jqXHR) { + console.error(jqXHR); + swal({ + type: 'error', + title: 'Whoops!', + text: 'An error occured while attempting to trigger this schedule.' + }); + }); + }); + }); + $('[data-action="toggle-schedule"]').click(function (event) { var self = $(this); swal({ diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php index 3aa27e5b2..4cb73a0f6 100644 --- a/resources/lang/en/server.php +++ b/resources/lang/en/server.php @@ -30,6 +30,9 @@ return [ 'command' => 'Send Command', 'power' => 'Power Action', ], + 'toggle' => 'Toggle Status', + 'run_now' => 'Trigger Schedule', + 'schedule_created' => 'Successfully created a new schedule for this server.', 'unnamed' => 'Unnamed Schedule', 'setup' => 'Schedule Setup', 'day_of_week' => 'Day of Week', diff --git a/resources/themes/pterodactyl/server/schedules/index.blade.php b/resources/themes/pterodactyl/server/schedules/index.blade.php index 65e5d678a..b51ee089c 100644 --- a/resources/themes/pterodactyl/server/schedules/index.blade.php +++ b/resources/themes/pterodactyl/server/schedules/index.blade.php @@ -6,7 +6,7 @@ @extends('layouts.master') @section('title') - @lang('server.schedules.header') + @lang('server.schedule.header') @endsection @section('content-header') @@ -38,7 +38,6 @@ @lang('strings.last_run') @lang('strings.next_run') - @foreach($schedules as $schedule) is_active)class="muted muted-hover"@endif> @@ -66,21 +65,20 @@ @if($schedule->is_active) - @if($schedule->last_run_at) - {{ Carbon::parse($schedule->next_run_at)->toDayDateTimeString() }}
({{ Carbon::parse($schedule->next_run_at)->diffForHumans() }}) - @else - @lang('strings.not_run_yet') - @endif + {{ Carbon::parse($schedule->next_run_at)->toDayDateTimeString() }}
({{ Carbon::parse($schedule->next_run_at)->diffForHumans() }}) @else n/a @endif - @can('delete-schedule', $server) - - @endcan - @can('toggle-schedule', $server) - - @endcan + + @can('delete-schedule', $server) + + @endcan + @can('toggle-schedule', $server) + + + @endcan + @endforeach diff --git a/routes/server.php b/routes/server.php index 31a707e15..85283df9e 100644 --- a/routes/server.php +++ b/routes/server.php @@ -96,7 +96,8 @@ Route::group(['prefix' => 'schedules'], function () { Route::get('/view/{schedule}', 'Tasks\TaskManagementController@view')->name('server.schedules.view'); Route::patch('/view/{schedule}', 'Tasks\TaskManagementController@update'); - Route::patch('/view/{schedule}/toggle', 'Tasks\TaskToggleController@index')->name('server.schedules.toggle'); + Route::post('/view/{schedule}/toggle', 'Tasks\ActionController@toggle')->name('server.schedules.toggle'); + Route::post('/view/{schedule}/trigger', 'Tasks\ActionController@trigger')->name('server.schedules.trigger'); Route::delete('/view/{schedule}', 'Tasks\TaskManagementController@delete'); }); diff --git a/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php b/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php index bd13a2c9a..f8de38031 100644 --- a/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php +++ b/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php @@ -34,7 +34,7 @@ class CleanServiceBackupFilesCommandTest extends CommandTestCase { parent::setUp(); - Carbon::setTestNow(); + Carbon::setTestNow(Carbon::now()); $this->disk = m::mock(Filesystem::class); $this->filesystem = m::mock(Factory::class); $this->filesystem->shouldReceive('disk')->withNoArgs()->once()->andReturn($this->disk); diff --git a/tests/Unit/Jobs/Schedule/RunTaskJobTest.php b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php index a32d7dfe1..a398f969f 100644 --- a/tests/Unit/Jobs/Schedule/RunTaskJobTest.php +++ b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Tests\Unit\Jobs\Schedule; @@ -87,9 +80,10 @@ class RunTaskJobTest extends TestCase $schedule = factory(Schedule::class)->make(); $task = factory(Task::class)->make(['action' => 'power', 'sequence_id' => 1]); $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); $server->setRelation('user', factory(User::class)->make()); - $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456'); $this->powerRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf() ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf() @@ -116,9 +110,10 @@ class RunTaskJobTest extends TestCase $schedule = factory(Schedule::class)->make(); $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); $server->setRelation('user', factory(User::class)->make()); - $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456'); $this->commandRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf() ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf() @@ -145,9 +140,10 @@ class RunTaskJobTest extends TestCase $schedule = factory(Schedule::class)->make(); $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); $server->setRelation('user', factory(User::class)->make()); - $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456'); $this->commandRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf() ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf() @@ -176,19 +172,45 @@ class RunTaskJobTest extends TestCase * Test that an exception is thrown if an invalid task action is supplied. * * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Cannot run a task that points to a non-existant action. + * @expectedExceptionMessage Cannot run a task that points to a non-existent action. */ public function testInvalidActionPassedToJob() { + $schedule = factory(Schedule::class)->make(); $task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]); $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); $server->setRelation('user', factory(User::class)->make()); - $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); $this->getJobInstance($task->id, 1234); } + /** + * Test that a schedule marked as disabled does not get processed. + */ + public function testScheduleMarkedAsDisabledDoesNotProcess() + { + $schedule = factory(Schedule::class)->make(['is_active' => false]); + $task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]); + $task->setRelation('server', $server = factory(Server::class)->make()); + $task->setRelation('schedule', $schedule); + $server->setRelation('user', factory(User::class)->make()); + + $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task); + + $this->scheduleRepository->shouldReceive('withoutFreshModel->update')->with($schedule->id, [ + 'is_processing' => false, + 'last_run_at' => Carbon::now()->toDateTimeString(), + ])->once()->andReturn(1); + + $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturn(1); + + $this->getJobInstance($task->id, $schedule->id); + $this->assertTrue(true); + } + /** * Run the job using the mocks provided. * diff --git a/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php b/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php index 5c1cddec4..67db3b626 100644 --- a/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php +++ b/tests/Unit/Services/Schedules/ProcessScheduleServiceTest.php @@ -1,15 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Tests\Unit\Services\Schedules; use Mockery as m; +use Carbon\Carbon; use Tests\TestCase; use Cron\CronExpression; use Pterodactyl\Models\Task; @@ -46,8 +40,8 @@ class ProcessScheduleServiceTest extends TestCase public function setUp() { parent::setUp(); + Carbon::setTestNow(Carbon::now()); - $this->cron = m::mock('overload:' . CronExpression::class); $this->repository = m::mock(ScheduleRepositoryInterface::class); $this->runnerService = m::mock(RunTaskService::class); @@ -64,14 +58,12 @@ class ProcessScheduleServiceTest extends TestCase 'sequence_id' => 1, ])])); + $this->repository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model); + $formatted = sprintf('%s %s %s * %s *', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week); - - $this->cron->shouldReceive('factory')->with($formatted)->once()->andReturnSelf() - ->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('00:00:00'); - $this->repository->shouldReceive('update')->with($model->id, [ 'is_processing' => true, - 'next_run_at' => '00:00:00', + 'next_run_at' => CronExpression::factory($formatted)->getNextRunDate(), ]); $this->runnerService->shouldReceive('handle')->with($task)->once()->andReturnNull(); @@ -80,58 +72,23 @@ class ProcessScheduleServiceTest extends TestCase $this->assertTrue(true); } - /** - * Test that passing a schedule model without a tasks relation is handled. - */ - public function testScheduleModelWithoutTasksIsHandled() - { - $nonRelationModel = factory(Schedule::class)->make(); - $model = clone $nonRelationModel; - $model->setRelation('tasks', collect([$task = factory(Task::class)->make([ - 'sequence_id' => 1, - ])])); - - $formatted = sprintf('%s %s %s * %s *', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week); - - $this->repository->shouldReceive('getScheduleWithTasks')->with($nonRelationModel->id)->once()->andReturn($model); - $this->cron->shouldReceive('factory')->with($formatted)->once()->andReturnSelf() - ->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('00:00:00'); - - $this->repository->shouldReceive('update')->with($model->id, [ - 'is_processing' => true, - 'next_run_at' => '00:00:00', - ]); - - $this->runnerService->shouldReceive('handle')->with($task)->once()->andReturnNull(); - - $this->service->handle($nonRelationModel); - $this->assertTrue(true); - } - - /** - * Test that a task ID can be passed in place of the task model. - */ - public function testPassingScheduleIdInPlaceOfModelIsHandled() + public function testScheduleRunTimeCanBeOverridden() { $model = factory(Schedule::class)->make(); $model->setRelation('tasks', collect([$task = factory(Task::class)->make([ 'sequence_id' => 1, ])])); - $formatted = sprintf('%s %s %s * %s *', $model->cron_minute, $model->cron_hour, $model->cron_day_of_month, $model->cron_day_of_week); - - $this->repository->shouldReceive('getScheduleWithTasks')->with($model->id)->once()->andReturn($model); - $this->cron->shouldReceive('factory')->with($formatted)->once()->andReturnSelf() - ->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('00:00:00'); + $this->repository->shouldReceive('loadTasks')->with($model)->once()->andReturn($model); $this->repository->shouldReceive('update')->with($model->id, [ 'is_processing' => true, - 'next_run_at' => '00:00:00', + 'next_run_at' => Carbon::now()->addSeconds(15)->toDateTimeString(), ]); $this->runnerService->shouldReceive('handle')->with($task)->once()->andReturnNull(); - $this->service->handle($model->id); + $this->service->setRunTimeOverride(Carbon::now()->addSeconds(15))->handle($model); $this->assertTrue(true); } } diff --git a/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php b/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php index 1e3747613..62d875fcd 100644 --- a/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php +++ b/tests/Unit/Services/Schedules/ScheduleCreationServiceTest.php @@ -1,17 +1,10 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Tests\Unit\Services\Schedules; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Models\Node; +use Cron\CronExpression; use Pterodactyl\Models\Server; use Pterodactyl\Models\Schedule; use Illuminate\Database\ConnectionInterface; @@ -54,7 +47,6 @@ class ScheduleCreationServiceTest extends TestCase parent::setUp(); $this->connection = m::mock(ConnectionInterface::class); - $this->cron = m::mock('overload:\Cron\CronExpression'); $this->repository = m::mock(ScheduleRepositoryInterface::class); $this->taskCreationService = m::mock(TaskCreationService::class); @@ -69,18 +61,15 @@ class ScheduleCreationServiceTest extends TestCase $schedule = factory(Schedule::class)->make(); $server = factory(Server::class)->make(); - $this->cron->shouldReceive('factory')->with('* * * * * *')->once()->andReturnSelf() - ->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('nextDate'); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->repository->shouldReceive('create')->with([ 'server_id' => $server->id, - 'next_run_at' => 'nextDate', - 'test_data' => 'test_value', + 'next_run_at' => CronExpression::factory('* * * * * *')->getNextRunDate(), + 'test_key' => 'value', ])->once()->andReturn($schedule); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - $response = $this->service->handle($server, ['test_data' => 'test_value']); - $this->assertNotEmpty($response); + $response = $this->service->handle($server, ['test_key' => 'value', 'server_id' => '123abc']); $this->assertInstanceOf(Schedule::class, $response); $this->assertEquals($schedule, $response); } @@ -93,14 +82,13 @@ class ScheduleCreationServiceTest extends TestCase $schedule = factory(Schedule::class)->make(); $server = factory(Server::class)->make(); - $this->cron->shouldReceive('factory')->with('* * * * * *')->once()->andReturnSelf() - ->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('nextDate'); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->repository->shouldReceive('create')->with([ - 'next_run_at' => 'nextDate', 'server_id' => $server->id, - 'test_data' => 'test_value', + 'next_run_at' => CronExpression::factory('* * * * * *')->getNextRunDate(), + 'test_key' => 'value', ])->once()->andReturn($schedule); + $this->taskCreationService->shouldReceive('handle')->with($schedule, [ 'time_interval' => 'm', 'time_value' => 10, @@ -111,62 +99,10 @@ class ScheduleCreationServiceTest extends TestCase $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - $response = $this->service->handle($server, ['test_data' => 'test_value'], [ + $response = $this->service->handle($server, ['test_key' => 'value'], [ ['time_interval' => 'm', 'time_value' => 10, 'action' => 'test', 'payload' => 'testpayload'], ]); - $this->assertNotEmpty($response); $this->assertInstanceOf(Schedule::class, $response); $this->assertEquals($schedule, $response); } - - /** - * Test that an ID can be passed in place of the server model. - */ - public function testIdCanBePassedInPlaceOfServerModel() - { - $schedule = factory(Schedule::class)->make(); - - $this->cron->shouldReceive('factory')->with('* * * * * *')->once()->andReturnSelf() - ->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('nextDate'); - $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->repository->shouldReceive('create')->with([ - 'next_run_at' => 'nextDate', - 'server_id' => 1234, - 'test_data' => 'test_value', - ])->once()->andReturn($schedule); - $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); - - $response = $this->service->handle(1234, ['test_data' => 'test_value']); - $this->assertNotEmpty($response); - $this->assertInstanceOf(Schedule::class, $response); - $this->assertEquals($schedule, $response); - } - - /** - * Test that an exception is raised if invalid data is passed. - * - * @dataProvider invalidServerArgumentProvider - * @expectedException \InvalidArgumentException - */ - public function testExceptionIsThrownIfServerIsInvalid($attribute) - { - $this->service->handle($attribute, []); - } - - /** - * Return an array of invalid server data to test aganist. - * - * @return array - */ - public function invalidServerArgumentProvider() - { - return [ - [123.456], - ['server'], - ['abc123'], - ['123_test'], - [new Node()], - [Server::class], - ]; - } }