diff --git a/.env.example b/.env.example
index c7a972be..45644374 100644
--- a/.env.example
+++ b/.env.example
@@ -16,6 +16,9 @@ DB_PASSWORD=secret
CACHE_DRIVER=file
SESSION_DRIVER=database
+HASHIDS_SALT=
+HASHIDS_LENGTH=8
+
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
diff --git a/.env.travis b/.env.travis
index 1b6ed1af..22a0c304 100644
--- a/.env.travis
+++ b/.env.travis
@@ -14,3 +14,5 @@ CACHE_DRIVER=array
SESSION_DRIVER=array
MAIL_DRIVER=array
QUEUE_DRIVER=sync
+
+HASHIDS_SALT=test123
diff --git a/app/Contracts/Extensions/HashidsInterface.php b/app/Contracts/Extensions/HashidsInterface.php
new file mode 100644
index 00000000..8630718f
--- /dev/null
+++ b/app/Contracts/Extensions/HashidsInterface.php
@@ -0,0 +1,41 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Extensions;
+
+use Hashids\HashidsInterface as VendorHashidsInterface;
+
+interface HashidsInterface extends VendorHashidsInterface
+{
+ /**
+ * Decode an encoded hashid and return the first result.
+ *
+ * @param string $encoded
+ * @param null $default
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function decodeFirst($encoded, $default = null);
+}
diff --git a/app/Contracts/Repository/TaskRepositoryInterface.php b/app/Contracts/Repository/TaskRepositoryInterface.php
new file mode 100644
index 00000000..8d8b32c3
--- /dev/null
+++ b/app/Contracts/Repository/TaskRepositoryInterface.php
@@ -0,0 +1,47 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Repository;
+
+interface TaskRepositoryInterface extends RepositoryInterface
+{
+ /**
+ * Return the parent tasks and the count of children attached to that task.
+ *
+ * @param int $server
+ * @return mixed
+ */
+ public function getParentTasksWithChainCount($server);
+
+ /**
+ * Return a single task for a given server including all of the chained tasks.
+ *
+ * @param int $task
+ * @param int $server
+ * @return mixed
+ *
+ * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function getTaskForServer($task, $server);
+}
diff --git a/app/Extensions/DynamicDatabaseConnection.php b/app/Extensions/DynamicDatabaseConnection.php
index 1e13e231..15328d05 100644
--- a/app/Extensions/DynamicDatabaseConnection.php
+++ b/app/Extensions/DynamicDatabaseConnection.php
@@ -73,6 +73,8 @@ class DynamicDatabaseConnection
* @param string $connection
* @param \Pterodactyl\Models\DatabaseHost|int $host
* @param string $database
+ *
+ * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function set($connection, $host, $database = 'mysql')
{
diff --git a/app/Extensions/Hashids.php b/app/Extensions/Hashids.php
new file mode 100644
index 00000000..49107a5e
--- /dev/null
+++ b/app/Extensions/Hashids.php
@@ -0,0 +1,44 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Extensions;
+
+use Hashids\Hashids as VendorHashids;
+use Pterodactyl\Contracts\Extensions\HashidsInterface;
+
+class Hashids extends VendorHashids implements HashidsInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function decodeFirst($encoded, $default = null)
+ {
+ $result = $this->decode($encoded);
+ if (! is_array($result)) {
+ return $default;
+ }
+
+ return array_first($result, null, $default);
+ }
+}
diff --git a/app/Http/Controllers/Server/Tasks/TaskManagementController.php b/app/Http/Controllers/Server/Tasks/TaskManagementController.php
new file mode 100644
index 00000000..80c2e667
--- /dev/null
+++ b/app/Http/Controllers/Server/Tasks/TaskManagementController.php
@@ -0,0 +1,171 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Http\Controllers\Server\Tasks;
+
+use Illuminate\Contracts\Session\Session;
+use Pterodactyl\Http\Controllers\Controller;
+use Pterodactyl\Services\Tasks\TaskCreationService;
+use Pterodactyl\Contracts\Extensions\HashidsInterface;
+use Pterodactyl\Traits\Controllers\JavascriptInjection;
+use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
+use Pterodactyl\Http\Requests\Server\TaskCreationFormRequest;
+
+class TaskManagementController extends Controller
+{
+ use JavascriptInjection;
+
+ /**
+ * @var \Pterodactyl\Services\Tasks\TaskCreationService
+ */
+ protected $creationService;
+
+ /**
+ * @var \Pterodactyl\Contracts\Extensions\HashidsInterface
+ */
+ protected $hashids;
+
+ /**
+ * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
+ */
+ protected $repository;
+
+ /**
+ * @var \Illuminate\Contracts\Session\Session
+ */
+ protected $session;
+
+ /**
+ * TaskManagementController constructor.
+ *
+ * @param \Pterodactyl\Contracts\Extensions\HashidsInterface $hashids
+ * @param \Illuminate\Contracts\Session\Session $session
+ * @param \Pterodactyl\Services\Tasks\TaskCreationService $creationService
+ * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository
+ */
+ public function __construct(
+ HashidsInterface $hashids,
+ Session $session,
+ TaskCreationService $creationService,
+ TaskRepositoryInterface $repository
+ ) {
+ $this->creationService = $creationService;
+ $this->hashids = $hashids;
+ $this->repository = $repository;
+ $this->session = $session;
+ }
+
+ /**
+ * Display the task page listing.
+ *
+ * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+ *
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ */
+ public function index()
+ {
+ $server = $this->session->get('server_data.model');
+ $this->authorize('list-tasks', $server);
+ $this->injectJavascript();
+
+ return view('server.tasks.index', [
+ 'tasks' => $this->repository->getParentTasksWithChainCount($server->id),
+ 'actions' => [
+ 'command' => trans('server.tasks.actions.command'),
+ 'power' => trans('server.tasks.actions.power'),
+ ],
+ ]);
+ }
+
+ /**
+ * Display the task creation page.
+ *
+ * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+ *
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ */
+ public function create()
+ {
+ $server = $this->session->get('server_data.model');
+ $this->authorize('create-task', $server);
+ $this->injectJavascript();
+
+ return view('server.tasks.new');
+ }
+
+ /**
+ * @param \Pterodactyl\Http\Requests\Server\TaskCreationFormRequest $request
+ *
+ * @return \Illuminate\Http\RedirectResponse
+ *
+ * @throws \Exception
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @throws \Pterodactyl\Exceptions\Model\DataValidationException
+ * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function store(TaskCreationFormRequest $request)
+ {
+ $server = $this->session->get('server_data.model');
+ $this->authorize('create-task', $server);
+
+ $task = $this->creationService->handle($server, $request->normalize(), $request->getChainedTasks());
+
+ return redirect()->route('server.tasks.view', [
+ 'server' => $server->uuidShort,
+ 'task' => $task->id,
+ ]);
+ }
+
+ /**
+ * Return a view to modify task settings.
+ *
+ * @param string $uuid
+ * @param string $task
+ * @return \Illuminate\View\View
+ *
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function view($uuid, $task)
+ {
+ $server = $this->session->get('server_data.model');
+ $this->authorize('edit-task', $server);
+ $task = $this->repository->getTaskForServer($this->hashids->decodeFirst($task, 0), $server->id);
+
+ $this->injectJavascript([
+ 'chained' => $task->chained->map(function ($chain) {
+ return collect($chain->toArray())->only('action', 'chain_delay', 'data')->all();
+ }),
+ ]);
+
+ return view('server.tasks.view', ['task' => $task]);
+ }
+
+ public function update(TaskCreationFormRequest $request, $uuid, $task)
+ {
+ $server = $this->session->get('server_data.model');
+ $this->authorize('edit-task', $server);
+ $task = $this->repository->getTaskForServer($this->hashids->decodeFirst($task, 0), $server->id);
+ }
+}
diff --git a/app/Http/Requests/Server/TaskCreationFormRequest.php b/app/Http/Requests/Server/TaskCreationFormRequest.php
new file mode 100644
index 00000000..486e04b3
--- /dev/null
+++ b/app/Http/Requests/Server/TaskCreationFormRequest.php
@@ -0,0 +1,84 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Http\Requests\Server;
+
+use Pterodactyl\Http\Requests\FrontendUserFormRequest;
+
+class TaskCreationFormRequest extends FrontendUserFormRequest
+{
+ /**
+ * Validation rules to apply to the request.
+ *
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+ 'name' => 'string|max:255',
+ 'day_of_week' => 'required|string',
+ 'day_of_month' => 'required|string',
+ 'hour' => 'required|string',
+ 'minute' => 'required|string',
+ 'action' => 'required|string|in:power,command',
+ 'data' => 'required|string',
+ 'chain' => 'sometimes|array|size:4',
+ 'chain.time_value' => 'required_with:chain|max:5',
+ 'chain.time_interval' => 'required_with:chain|max:5',
+ 'chain.action' => 'required_with:chain|max:5',
+ 'chain.payload' => 'required_with:chain|max:5',
+ 'chain.time_value.*' => 'numeric|between:1,60',
+ 'chain.time_interval.*' => 'string|in:s,m',
+ 'chain.action.*' => 'string|in:power,command',
+ 'chain.payload.*' => 'string',
+ ];
+ }
+
+ /**
+ * Normalize the request into a format that can be used by the application.
+ *
+ * @return array
+ */
+ public function normalize()
+ {
+ return $this->only('name', 'day_of_week', 'day_of_month', 'hour', 'minute', 'action', 'data');
+ }
+
+ /**
+ * Return the chained tasks provided in the request.
+ *
+ * @return array|null
+ */
+ public function getChainedTasks()
+ {
+ $restructured = [];
+ foreach (array_get($this->all(), 'chain', []) as $key => $values) {
+ for ($i = 0; $i < count($values); ++$i) {
+ $restructured[$i][$key] = $values[$i];
+ }
+ }
+
+ return empty($restructured) ? null : $restructured;
+ }
+}
diff --git a/app/Models/Task.php b/app/Models/Task.php
index 5e44a826..71a58251 100644
--- a/app/Models/Task.php
+++ b/app/Models/Task.php
@@ -24,10 +24,16 @@
namespace Pterodactyl\Models;
+use Sofa\Eloquence\Eloquence;
+use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model;
+use Sofa\Eloquence\Contracts\CleansAttributes;
+use Sofa\Eloquence\Contracts\Validable as ValidableContract;
-class Task extends Model
+class Task extends Model implements CleansAttributes, ValidableContract
{
+ use Eloquence, Validable;
+
/**
* The table associated with the model.
*
@@ -55,6 +61,53 @@ class Task extends Model
'active' => 'boolean',
];
+ /**
+ * Default attributes when creating a new model.
+ *
+ * @var array
+ */
+ protected $attributes = [
+ 'parent_task_id' => null,
+ 'chain_order' => null,
+ 'active' => true,
+ 'day_of_week' => '*',
+ 'day_of_month' => '*',
+ 'hour' => '*',
+ 'minute' => '*',
+ 'chain_delay' => null,
+ 'queued' => false,
+ ];
+
+ /**
+ * @var array
+ */
+ protected static $applicationRules = [
+ 'server_id' => 'required',
+ 'action' => 'required',
+ 'data' => 'required',
+ ];
+
+ /**
+ * @var array
+ */
+ protected static $dataIntegrityRules = [
+ 'name' => 'nullable|string|max:255',
+ 'parent_task_id' => 'nullable|numeric|exists:tasks,id',
+ 'chain_order' => 'nullable|numeric|min:1',
+ 'server_id' => 'numeric|exists:servers,id',
+ 'active' => 'boolean',
+ 'action' => 'string',
+ 'data' => 'string',
+ 'queued' => 'boolean',
+ 'day_of_month' => 'string',
+ 'day_of_week' => 'string',
+ 'hour' => 'string',
+ 'minute' => 'string',
+ 'chain_delay' => 'nullable|numeric|between:1,900',
+ 'last_run' => 'nullable|timestamp',
+ 'next_run' => 'nullable|timestamp',
+ ];
+
/**
* The attributes that should be mutated to dates.
*
@@ -62,6 +115,16 @@ class Task extends Model
*/
protected $dates = ['last_run', 'next_run', 'created_at', 'updated_at'];
+ /**
+ * Return a hashid encoded string to represent the ID of the task.
+ *
+ * @return string
+ */
+ public function getHashidAttribute()
+ {
+ return app()->make('hashids')->encode($this->id);
+ }
+
/**
* Gets the server associated with a task.
*
@@ -81,4 +144,14 @@ class Task extends Model
{
return $this->belongsTo(User::class);
}
+
+ /**
+ * Return chained tasks for a parent task.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function chained()
+ {
+ return $this->hasMany(self::class, 'parent_task_id')->orderBy('chain_order', 'asc');
+ }
}
diff --git a/app/Providers/HashidsServiceProvider.php b/app/Providers/HashidsServiceProvider.php
new file mode 100644
index 00000000..ee28104e
--- /dev/null
+++ b/app/Providers/HashidsServiceProvider.php
@@ -0,0 +1,51 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Providers;
+
+use Pterodactyl\Extensions\Hashids;
+use Illuminate\Support\ServiceProvider;
+use Pterodactyl\Contracts\Extensions\HashidsInterface;
+
+class HashidsServiceProvider extends ServiceProvider
+{
+ /**
+ * Register the ability to use Hashids.
+ */
+ public function register()
+ {
+ $this->app->singleton(HashidsInterface::class, function () {
+ /** @var \Illuminate\Contracts\Config\Repository $config */
+ $config = $this->app['config'];
+
+ return new Hashids(
+ $config->get('hashids.salt', ''),
+ $config->get('hashids.length', 0),
+ $config->get('hashids.alphabet', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890')
+ );
+ });
+
+ $this->app->alias(HashidsInterface::class, 'hashids');
+ }
+}
diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php
index 09c1c295..f44715df 100644
--- a/app/Providers/RepositoryServiceProvider.php
+++ b/app/Providers/RepositoryServiceProvider.php
@@ -29,6 +29,7 @@ use Pterodactyl\Repositories\Daemon\FileRepository;
use Pterodactyl\Repositories\Daemon\PowerRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\PackRepository;
+use Pterodactyl\Repositories\Eloquent\TaskRepository;
use Pterodactyl\Repositories\Eloquent\UserRepository;
use Pterodactyl\Repositories\Daemon\CommandRepository;
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
@@ -43,6 +44,7 @@ use Pterodactyl\Repositories\Eloquent\PermissionRepository;
use Pterodactyl\Repositories\Daemon\ConfigurationRepository;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
+use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
use Pterodactyl\Repositories\Eloquent\ApiPermissionRepository;
@@ -97,6 +99,7 @@ class RepositoryServiceProvider extends ServiceProvider
$this->app->bind(ServiceVariableRepositoryInterface::class, ServiceVariableRepository::class);
$this->app->bind(SessionRepositoryInterface::class, SessionRepository::class);
$this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class);
+ $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class);
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
// Daemon Repositories
diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php
new file mode 100644
index 00000000..3cc5b10a
--- /dev/null
+++ b/app/Repositories/Eloquent/TaskRepository.php
@@ -0,0 +1,74 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Repositories\Eloquent;
+
+use Pterodactyl\Models\Task;
+use Webmozart\Assert\Assert;
+use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
+use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
+
+class TaskRepository extends EloquentRepository implements TaskRepositoryInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function model()
+ {
+ return Task::class;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParentTasksWithChainCount($server)
+ {
+ Assert::numeric($server, 'First argument passed to GetParentTasksWithChainCount must be numeric, received %s.');
+
+ return $this->getBuilder()->withCount('chained')->where([
+ ['server_id', '=', $server],
+ ['parent_task_id', '=', null],
+ ])->get($this->getColumns());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTaskForServer($task, $server)
+ {
+ Assert::numeric($task, 'First argument passed to getTaskForServer must be numeric, received %s.');
+ Assert::numeric($server, 'Second argument passed to getTaskForServer must be numeric, received %s.');
+
+ $instance = $this->getBuilder()->with('chained')->where([
+ ['server_id', '=', $server],
+ ['parent_task_id', '=', null],
+ ])->find($task, $this->getColumns());
+
+ if (! $instance) {
+ throw new RecordNotFoundException;
+ }
+
+ return $instance;
+ }
+}
diff --git a/app/Services/Tasks/TaskCreationService.php b/app/Services/Tasks/TaskCreationService.php
new file mode 100644
index 00000000..d7549bf0
--- /dev/null
+++ b/app/Services/Tasks/TaskCreationService.php
@@ -0,0 +1,108 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Services\Tasks;
+
+use Pterodactyl\Models\Server;
+use Illuminate\Database\ConnectionInterface;
+use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
+use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+
+class TaskCreationService
+{
+ /**
+ * @var \Illuminate\Database\ConnectionInterface
+ */
+ protected $connection;
+
+ /**
+ * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
+ */
+ protected $repository;
+
+ /**
+ * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
+ */
+ protected $serverRepository;
+
+ /**
+ * TaskCreationService constructor.
+ *
+ * @param \Illuminate\Database\ConnectionInterface $connection
+ * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository
+ * @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository
+ */
+ public function __construct(
+ ConnectionInterface $connection,
+ ServerRepositoryInterface $serverRepository,
+ TaskRepositoryInterface $repository
+ ) {
+ $this->connection = $connection;
+ $this->repository = $repository;
+ $this->serverRepository = $serverRepository;
+ }
+
+ /**
+ * @param int|\Pterodactyl\Models\Server $server
+ * @param array $data
+ * @param array|null $chain
+ * @return \Pterodactyl\Models\Task
+ *
+ * @throws \Exception
+ * @throws \Pterodactyl\Exceptions\Model\DataValidationException
+ * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
+ */
+ public function handle($server, array $data, array $chain = null)
+ {
+ if (! $server instanceof Server) {
+ $server = $this->serverRepository->find($server);
+ }
+
+ $this->connection->beginTransaction();
+
+ $data['server_id'] = $server->id;
+ $task = $this->repository->create($data);
+
+ if (is_array($chain)) {
+ foreach ($chain as $index => $values) {
+ if ($values['time_interval'] === 'm' && $values['time_value'] > 15) {
+ throw new \Exception('I should fix this.');
+ }
+
+ $delay = $values['time_interval'] === 'm' ? $values['time_value'] * 60 : $values['time_value'];
+ $this->repository->withoutFresh()->create([
+ 'parent_task_id' => $task->id,
+ 'chain_order' => $index + 1,
+ 'server_id' => $server->id,
+ 'action' => $values['action'],
+ 'data' => $values['payload'],
+ 'chain_delay' => $delay,
+ ]);
+ }
+ }
+ $this->connection->commit();
+
+ return $task;
+ }
+}
diff --git a/app/Services/Tasks/TaskUpdateService.php b/app/Services/Tasks/TaskUpdateService.php
new file mode 100644
index 00000000..af227fdc
--- /dev/null
+++ b/app/Services/Tasks/TaskUpdateService.php
@@ -0,0 +1,47 @@
+.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Services\Tasks;
+
+use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
+use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+
+class TaskUpdateService
+{
+ protected $repository;
+
+ protected $serverRepository;
+
+ public function __construct(
+ ServerRepositoryInterface $serverRepository,
+ TaskRepositoryInterface $repository
+ ) {
+ $this->repository = $repository;
+ $this->serverRepository = $serverRepository;
+ }
+
+ public function handle($server, array $data, array $chain = null)
+ {
+ }
+}
diff --git a/composer.json b/composer.json
index 94ccfeee..a81baeec 100644
--- a/composer.json
+++ b/composer.json
@@ -21,6 +21,7 @@
"edvinaskrucas/settings": "^2.0",
"fideloper/proxy": "^3.3",
"guzzlehttp/guzzle": "~6.3.0",
+ "hashids/hashids": "^2.0",
"igaster/laravel-theme": "^1.16",
"laracasts/utilities": "^3.0",
"laravel/framework": "5.4.27",
diff --git a/composer.lock b/composer.lock
index eecee6ea..5b4809a0 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "a0014dfc711e382fff7903d9aeaffc25",
+ "content-hash": "15a4dc6de122bc1e47d1d9ca3b1224d6",
"packages": [
{
"name": "aws/aws-sdk-php",
@@ -1019,6 +1019,69 @@
],
"time": "2017-03-20T17:10:46+00:00"
},
+ {
+ "name": "hashids/hashids",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ivanakimov/hashids.php.git",
+ "reference": "28889ed83cdc91f4a55637daff0fb5c799eb324e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ivanakimov/hashids.php/zipball/28889ed83cdc91f4a55637daff0fb5c799eb324e",
+ "reference": "28889ed83cdc91f4a55637daff0fb5c799eb324e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-bcmath": "*",
+ "php": "^5.6.4 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hashids\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Akimov",
+ "email": "ivan@barreleye.com",
+ "homepage": "https://twitter.com/IvanAkimov"
+ },
+ {
+ "name": "Vincent Klaiber",
+ "email": "hello@vinkla.com",
+ "homepage": "https://vinkla.com"
+ }
+ ],
+ "description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers",
+ "homepage": "http://hashids.org/php",
+ "keywords": [
+ "bitly",
+ "decode",
+ "encode",
+ "hash",
+ "hashid",
+ "hashids",
+ "ids",
+ "obfuscate",
+ "youtube"
+ ],
+ "time": "2017-01-01T13:33:33+00:00"
+ },
{
"name": "igaster/laravel-theme",
"version": "v1.16",
diff --git a/config/app.php b/config/app.php
index b0e36cbc..e3cb7170 100644
--- a/config/app.php
+++ b/config/app.php
@@ -161,6 +161,7 @@ return [
Pterodactyl\Providers\AppServiceProvider::class,
Pterodactyl\Providers\AuthServiceProvider::class,
Pterodactyl\Providers\EventServiceProvider::class,
+ Pterodactyl\Providers\HashidsServiceProvider::class,
Pterodactyl\Providers\RouteServiceProvider::class,
Pterodactyl\Providers\MacroServiceProvider::class,
Pterodactyl\Providers\PhraseAppTranslationProvider::class,
@@ -237,7 +238,6 @@ return [
'URL' => Illuminate\Support\Facades\URL::class,
'Uuid' => Webpatser\Uuid\Uuid::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
- 'Version' => Pterodactyl\Facades\Version::class,
'View' => Illuminate\Support\Facades\View::class,
],
];
diff --git a/config/hashids.php b/config/hashids.php
new file mode 100644
index 00000000..199de1a3
--- /dev/null
+++ b/config/hashids.php
@@ -0,0 +1,15 @@
+ env('HASHIDS_SALT'),
+ 'length' => env('HASHIDS_LENGTH', 8),
+ 'alphabet' => env('HASHIDS_ALPHABET', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'),
+];
diff --git a/config/ide-helper.php b/config/ide-helper.php
new file mode 100644
index 00000000..9f10873f
--- /dev/null
+++ b/config/ide-helper.php
@@ -0,0 +1,175 @@
+ '_ide_helper',
+ 'format' => 'php',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Fluent helpers
+ |--------------------------------------------------------------------------
+ |
+ | Set to true to generate commonly used Fluent methods
+ |
+ */
+
+ 'include_fluent' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Write Model Magic methods
+ |--------------------------------------------------------------------------
+ |
+ | Set to false to disable write magic methods of model
+ |
+ */
+
+ 'write_model_magic_where' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Helper files to include
+ |--------------------------------------------------------------------------
+ |
+ | Include helper files. By default not included, but can be toggled with the
+ | -- helpers (-H) option. Extra helper files can be included.
+ |
+ */
+
+ 'include_helpers' => false,
+
+ 'helper_files' => [
+ base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Model locations to include
+ |--------------------------------------------------------------------------
+ |
+ | Define in which directories the ide-helper:models command should look
+ | for models.
+ |
+ */
+
+ 'model_locations' => [
+ 'app/Models',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Extra classes
+ |--------------------------------------------------------------------------
+ |
+ | These implementations are not really extended, but called with magic functions
+ |
+ */
+
+ 'extra' => [
+ 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'],
+ 'Session' => ['Illuminate\Session\Store'],
+ ],
+
+ 'magic' => [
+ 'Log' => [
+ 'debug' => 'Monolog\Logger::addDebug',
+ 'info' => 'Monolog\Logger::addInfo',
+ 'notice' => 'Monolog\Logger::addNotice',
+ 'warning' => 'Monolog\Logger::addWarning',
+ 'error' => 'Monolog\Logger::addError',
+ 'critical' => 'Monolog\Logger::addCritical',
+ 'alert' => 'Monolog\Logger::addAlert',
+ 'emergency' => 'Monolog\Logger::addEmergency',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Interface implementations
+ |--------------------------------------------------------------------------
+ |
+ | These interfaces will be replaced with the implementing class. Some interfaces
+ | are detected by the helpers, others can be listed below.
+ |
+ */
+
+ 'interfaces' => [
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Support for custom DB types
+ |--------------------------------------------------------------------------
+ |
+ | This setting allow you to map any custom database type (that you may have
+ | created using CREATE TYPE statement or imported using database plugin
+ | / extension to a Doctrine type.
+ |
+ | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are:
+ | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql'
+ |
+ | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant
+ |
+ | The value of the array is an array of type mappings. Key is the name of the custom type,
+ | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in
+ | our case it is 'json_array'. Doctrine types are listed here:
+ | http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html
+ |
+ | So to support jsonb in your models when working with Postgres, just add the following entry to the array below:
+ |
+ | "postgresql" => array(
+ | "jsonb" => "json_array",
+ | ),
+ |
+ */
+ 'custom_db_types' => [
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Support for camel cased models
+ |--------------------------------------------------------------------------
+ |
+ | There are some Laravel packages (such as Eloquence) that allow for accessing
+ | Eloquent model properties via camel case, instead of snake case.
+ |
+ | Enabling this option will support these packages by saving all model
+ | properties as camel case, instead of snake case.
+ |
+ | For example, normally you would see this:
+ |
+ | * @property \Carbon\Carbon $created_at
+ | * @property \Carbon\Carbon $updated_at
+ |
+ | With this enabled, the properties will be this:
+ |
+ | * @property \Carbon\Carbon $createdAt
+ | * @property \Carbon\Carbon $updatedAt
+ |
+ | Note, it is currently an all-or-nothing option.
+ |
+ */
+ 'model_camel_case_properties' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Property Casts
+ |--------------------------------------------------------------------------
+ |
+ | Cast the given "real type" to the given "type".
+ |
+ */
+ 'type_overrides' => [
+ 'integer' => 'int',
+ 'boolean' => 'bool',
+ ],
+];
diff --git a/database/migrations/2017_09_09_125253_AddChainedTasksAbility.php b/database/migrations/2017_09_09_125253_AddChainedTasksAbility.php
new file mode 100644
index 00000000..8748e409
--- /dev/null
+++ b/database/migrations/2017_09_09_125253_AddChainedTasksAbility.php
@@ -0,0 +1,49 @@
+unsignedInteger('parent_task_id')->after('id')->nullable();
+ $table->unsignedInteger('chain_order')->after('parent_task_id')->nullable();
+ $table->unsignedInteger('chain_delay')->after('minute')->nullable();
+ $table->string('name')->after('server_id')->nullable();
+
+ $table->foreign('parent_task_id')->references('id')->on('tasks')->onDelete('cascade');
+ $table->index(['parent_task_id', 'chain_order']);
+
+ $table->dropForeign(['user_id']);
+ $table->dropColumn('user_id');
+ $table->dropColumn('year');
+ $table->dropColumn('month');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down()
+ {
+ Schema::table('tasks', function (Blueprint $table) {
+ $table->dropForeign(['parent_task_id']);
+ $table->dropIndex(['parent_task_id', 'chain_order']);
+ $table->dropColumn('parent_task_id');
+ $table->dropColumn('chain_order');
+ $table->dropColumn('chain_delay');
+ $table->dropColumn('name');
+
+ $table->unsignedInteger('user_id')->after('id')->nullable();
+ $table->string('year')->after('queued')->default('*');
+ $table->string('month')->after('year')->default('*');
+ $table->foreign('user_id')->references('id')->on('users');
+ });
+ }
+}
diff --git a/database/migrations/2017_09_09_162204_AddNullableNextRunColumn.php b/database/migrations/2017_09_09_162204_AddNullableNextRunColumn.php
new file mode 100644
index 00000000..40adb401
--- /dev/null
+++ b/database/migrations/2017_09_09_162204_AddNullableNextRunColumn.php
@@ -0,0 +1,31 @@
+wrapTable('tasks');
+ DB::statement('ALTER TABLE ' . $table . ' CHANGE `next_run` `next_run` TIMESTAMP NULL;');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down()
+ {
+ Schema::table('tasks', function (Blueprint $table) {
+ $table = DB::getQueryGrammar()->wrapTable('tasks');
+ DB::statement('ALTER TABLE ' . $table . ' CHANGE `next_run` `next_run` TIMESTAMP NOT NULL;');
+ });
+ }
+}
diff --git a/public/themes/pterodactyl/js/frontend/tasks.js b/public/themes/pterodactyl/js/frontend/tasks.js
index b19e1955..8b600a26 100644
--- a/public/themes/pterodactyl/js/frontend/tasks.js
+++ b/public/themes/pterodactyl/js/frontend/tasks.js
@@ -18,98 +18,113 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-var Tasks = (function () {
-
- function initTaskFunctions() {
- $('[data-action="delete-task"]').click(function (event) {
- var self = $(this);
- swal({
- type: 'error',
- title: 'Delete Task?',
- text: 'Are you sure you want to delete this task? There is no undo.',
- showCancelButton: true,
- allowOutsideClick: true,
- closeOnConfirm: false,
- confirmButtonText: 'Delete Task',
- confirmButtonColor: '#d9534f',
- showLoaderOnConfirm: true
- }, function () {
- $.ajax({
- method: 'DELETE',
- url: Router.route('server.tasks.delete', {
- server: Pterodactyl.server.uuidShort,
- id: self.data('id'),
- }),
- headers: {
- 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
- }
- }).done(function (data) {
- swal({
- type: 'success',
- title: '',
- text: 'Task has been deleted.'
- });
- self.parent().parent().slideUp();
- }).fail(function (jqXHR) {
- console.error(jqXHR);
- swal({
- type: 'error',
- title: 'Whoops!',
- text: 'An error occured while attempting to delete this task.'
- });
- });
- });
- });
- $('[data-action="toggle-task"]').click(function (event) {
- var self = $(this);
- swal({
- type: 'info',
- title: 'Toggle Task',
- text: 'This will toggle the selected task.',
- showCancelButton: true,
- allowOutsideClick: true,
- closeOnConfirm: false,
- confirmButtonText: 'Continue',
- showLoaderOnConfirm: true
- }, function () {
- $.ajax({
- method: 'POST',
- url: Router.route('server.tasks.toggle', {
- server: Pterodactyl.server.uuidShort,
- id: self.data('id'),
- }),
- headers: {
- 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
- }
- }).done(function (data) {
- swal({
- type: 'success',
- title: '',
- text: 'Task has been toggled.'
- });
- if (data.status !== 1) {
- self.parent().parent().addClass('muted muted-hover');
- } else {
- self.parent().parent().removeClass('muted muted-hover');
- }
- }).fail(function (jqXHR) {
- console.error(jqXHR);
- swal({
- type: 'error',
- title: 'Whoops!',
- text: 'An error occured while attempting to toggle this task.'
- });
- });
- });
- });
- }
-
- return {
- init: function () {
- initTaskFunctions();
+$(document).ready(function () {
+ $('select[name="action"]').select2();
+ $('[data-action="update-field"]').on('change', function (event) {
+ event.preventDefault();
+ var updateField = $(this).data('field');
+ var selected = $(this).map(function (i, opt) {
+ return $(opt).val();
+ }).toArray();
+ if (selected.length === $(this).find('option').length) {
+ $('input[name=' + updateField + ']').val('*');
+ } else {
+ $('input[name=' + updateField + ']').val(selected.join(','));
}
- }
+ });
-})();
+ $('button[data-action="add-chain"]').on('click', function () {
+ var clone = $('div[data-target="chain-clone"]').clone();
+ clone.insertBefore('#chainLastSegment').removeAttr('data-target').removeClass('hidden');
+ clone.find('select[name="chain[time_value][]"]').select2();
+ clone.find('select[name="chain[time_interval][]"]').select2();
+ clone.find('select[name="chain[action][]"]').select2();
+ clone.find('button[data-action="remove-chain-element"]').on('click', function () {
+ clone.remove();
+ });
+ $(this).data('element', clone);
+ });
-Tasks.init();
+ $('[data-action="delete-task"]').click(function () {
+ var self = $(this);
+ swal({
+ type: 'error',
+ title: 'Delete Task?',
+ text: 'Are you sure you want to delete this task? There is no undo.',
+ showCancelButton: true,
+ allowOutsideClick: true,
+ closeOnConfirm: false,
+ confirmButtonText: 'Delete Task',
+ confirmButtonColor: '#d9534f',
+ showLoaderOnConfirm: true
+ }, function () {
+ $.ajax({
+ method: 'DELETE',
+ url: Router.route('server.tasks.delete', {
+ server: Pterodactyl.server.uuidShort,
+ id: self.data('id'),
+ }),
+ headers: {
+ 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
+ }
+ }).done(function (data) {
+ swal({
+ type: 'success',
+ title: '',
+ text: 'Task has been deleted.'
+ });
+ self.parent().parent().slideUp();
+ }).fail(function (jqXHR) {
+ console.error(jqXHR);
+ swal({
+ type: 'error',
+ title: 'Whoops!',
+ text: 'An error occured while attempting to delete this task.'
+ });
+ });
+ });
+ });
+
+ $('[data-action="toggle-task"]').click(function (event) {
+ var self = $(this);
+ swal({
+ type: 'info',
+ title: 'Toggle Task',
+ text: 'This will toggle the selected task.',
+ showCancelButton: true,
+ allowOutsideClick: true,
+ closeOnConfirm: false,
+ confirmButtonText: 'Continue',
+ showLoaderOnConfirm: true
+ }, function () {
+ $.ajax({
+ method: 'POST',
+ url: Router.route('server.tasks.toggle', {
+ server: Pterodactyl.server.uuidShort,
+ id: self.data('id'),
+ }),
+ headers: {
+ 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content'),
+ }
+ }).done(function (data) {
+ swal({
+ type: 'success',
+ title: '',
+ text: 'Task has been toggled.'
+ });
+ if (data.status !== 1) {
+ self.parent().parent().addClass('muted muted-hover');
+ } else {
+ self.parent().parent().removeClass('muted muted-hover');
+ }
+ }).fail(function (jqXHR) {
+ console.error(jqXHR);
+ swal({
+ type: 'error',
+ title: 'Whoops!',
+ text: 'An error occured while attempting to toggle this task.'
+ });
+ });
+ });
+ });
+});
diff --git a/resources/lang/en/server.php b/resources/lang/en/server.php
index e62b94d3..8e750324 100644
--- a/resources/lang/en/server.php
+++ b/resources/lang/en/server.php
@@ -19,6 +19,7 @@ return [
'new' => [
'header' => 'New Task',
'header_sub' => 'Create a new scheduled task for this server.',
+ 'task_name' => 'Task Name',
'day_of_week' => 'Day of Week',
'custom' => 'Custom Value',
'day_of_month' => 'Day of Month',
@@ -33,9 +34,16 @@ return [
'sat' => 'Saturday',
'submit' => 'Create Task',
'type' => 'Task Type',
+ 'chain_then' => 'Then, After',
+ 'chain_do' => 'Do',
+ 'chain_arguments' => 'With Arguments',
'payload' => 'Task Payload',
'payload_help' => 'For example, if you selected Send Command
enter the command here. If you selected Send Power Option
put the power action here (e.g. restart
).',
],
+ 'edit' => [
+ 'header' => 'Manage Task',
+ 'submit' => 'Update Task',
+ ],
],
'users' => [
'header' => 'Manage Users',
diff --git a/resources/lang/en/strings.php b/resources/lang/en/strings.php
index 864cdf2e..8cdb400c 100644
--- a/resources/lang/en/strings.php
+++ b/resources/lang/en/strings.php
@@ -71,4 +71,7 @@ return [
'admin' => 'Admin',
'subuser' => 'Subuser',
'captcha_invalid' => 'The provided captcha is invalid.',
+ 'child_tasks' => 'Child Tasks',
+ 'seconds' => 'Seconds',
+ 'minutes' => 'Minutes',
];
diff --git a/resources/themes/pterodactyl/layouts/master.blade.php b/resources/themes/pterodactyl/layouts/master.blade.php
index 6199f3d8..df5b4284 100644
--- a/resources/themes/pterodactyl/layouts/master.blade.php
+++ b/resources/themes/pterodactyl/layouts/master.blade.php
@@ -146,7 +146,7 @@
@endcan
@can('list-subusers', $server)
@lang('strings.action') | -@lang('strings.data') | -@lang('strings.queued') | +@lang('strings.name') | +@lang('strings.queued') | +@lang('strings.child_tasks') | @lang('strings.last_run') | @lang('strings.next_run') | @@ -56,25 +56,35 @@ |
---|---|---|---|---|---|---|---|---|
{{ $actions[$task->action] }} | -{{ $task->data }} |
+ @can('edit-task', $server) + {{ $task->name }} + @else + {{ $task->name }} + @endcan + | +@if ($task->queued) @lang('strings.yes') @else @lang('strings.no') @endif | +{{ $task->chained_count }} |
@if($task->last_run)
{{ Carbon::parse($task->last_run)->toDayDateTimeString() }} ({{ Carbon::parse($task->last_run)->diffForHumans() }}) - @else - @lang('strings.not_run_yet') + @else + @lang('strings.not_run_yet') @endif |
@if($task->active !== 0)
- {{ Carbon::parse($task->next_run)->toDayDateTimeString() }} ({{ Carbon::parse($task->next_run)->diffForHumans() }}) + @if($task->last_run) + {{ Carbon::parse($task->next_run)->toDayDateTimeString() }} ({{ Carbon::parse($task->next_run)->diffForHumans() }}) + @else + @lang('strings.not_run_yet') + @endif @else n/a @endif diff --git a/resources/themes/pterodactyl/server/tasks/new.blade.php b/resources/themes/pterodactyl/server/tasks/new.blade.php index 045d2fe8..baa69b87 100644 --- a/resources/themes/pterodactyl/server/tasks/new.blade.php +++ b/resources/themes/pterodactyl/server/tasks/new.blade.php @@ -41,6 +41,22 @@ @section('content') +@include('partials.tasks.chain-template') @endsection @section('footer-scripts') @parent {!! Theme::js('js/frontend/server.socket.js') !!} {!! Theme::js('vendor/select2/select2.full.min.js') !!} - + {!! Theme::js('js/frontend/tasks.js') !!} @endsection diff --git a/resources/themes/pterodactyl/server/tasks/view.blade.php b/resources/themes/pterodactyl/server/tasks/view.blade.php new file mode 100644 index 00000000..b58106be --- /dev/null +++ b/resources/themes/pterodactyl/server/tasks/view.blade.php @@ -0,0 +1,230 @@ +{{-- Copyright (c) 2015 - 2017 Dane Everitt @lang('server.tasks.edit.header'){{ $task->name }}+ +@endsection + +@section('content') + +@include('partials.tasks.chain-template') +@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('js/frontend/server.socket.js') !!} + {!! Theme::js('vendor/select2/select2.full.min.js') !!} + {!! Theme::js('js/frontend/tasks.js') !!} + +@endsection diff --git a/resources/themes/pterodactyl/vendor/.gitkeep b/resources/themes/pterodactyl/vendor/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/routes/server.php b/routes/server.php index a79309c2..3d479d4c 100644 --- a/routes/server.php +++ b/routes/server.php @@ -79,7 +79,7 @@ Route::group(['prefix' => 'users'], function () { Route::patch('/view/{subuser}', 'SubuserController@update')->middleware(SubuserAccess::class); - Route::delete('/delete/{subuser}', 'SubuserController@delete')->middleware(SubuserAccess::class)->name('server.subusers.delete'); + Route::delete('/view/{subuser}/delete', 'SubuserController@delete')->middleware(SubuserAccess::class)->name('server.subusers.delete'); }); /* @@ -91,13 +91,16 @@ Route::group(['prefix' => 'users'], function () { | */ Route::group(['prefix' => 'tasks'], function () { - Route::get('/', 'TaskController@index')->name('server.tasks'); - Route::get('/new', 'TaskController@create')->name('server.tasks.new'); + Route::get('/', 'Tasks\TaskManagementController@index')->name('server.tasks'); + Route::get('/new', 'Tasks\TaskManagementController@create')->name('server.tasks.new'); + Route::get('/view/{task}', 'Tasks\TaskManagementController@view')->name('server.tasks.view'); - Route::post('/new', 'TaskController@store'); - Route::post('/toggle/{id}', 'TaskController@toggle')->name('server.tasks.toggle'); + Route::post('/new', 'Tasks\TaskManagementController@store'); - Route::delete('/delete/{id}', 'TaskController@delete')->name('server.tasks.delete'); + Route::patch('/view/{task}', 'Tasks\TaskManagementController@update'); + Route::patch('/view/{task}/toggle', 'Tasks\ToggleTaskController@index')->name('server.tasks.toggle'); + + Route::delete('/view/{task}/delete', 'Tasks\TaskManagementController@delete')->name('server.tasks.delete'); }); /* @@ -109,6 +112,5 @@ Route::group(['prefix' => 'tasks'], function () { | */ Route::group(['prefix' => 'ajax'], function () { - Route::post('/set-primary', 'AjaxController@postSetPrimary')->name('server.ajax.set-primary'); Route::post('/settings/reset-database-password', 'AjaxController@postResetDatabasePassword')->name('server.ajax.reset-database-password'); }); |