diff --git a/app/Http/Controllers/Server/TaskController.php b/app/Http/Controllers/Server/TaskController.php new file mode 100644 index 000000000..220f7ff18 --- /dev/null +++ b/app/Http/Controllers/Server/TaskController.php @@ -0,0 +1,87 @@ + + * + * 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; + +use Alert; +use Log; +use Cron; + +use Pterodactyl\Models; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Http\Request; + +class TaskController extends Controller +{ + public function __constructor() + { + // + } + + public function getIndex(Request $request, $uuid) + { + $server = Models\Server::getByUUID($uuid); + $this->authorize('list-tasks', $server); + + return view('server.tasks.index', [ + 'server' => $server, + 'node' => Models\Node::findOrFail($server->node), + 'tasks' => Models\Task::where('server', $server->id)->get(), + 'actions' => [ + 'command' => 'Send Command', + 'power' => 'Set Power Status' + ] + ]); + } + + public function getNew(Request $request, $uuid) + { + $server = Models\Server::getByUUID($uuid); + $this->authorize('create-task', $server); + + return view('server.tasks.new', [ + 'server' => $server, + 'node' => Models\Node::findOrFail($server->node) + ]); + } + + public function postNew(Request $request, $uuid) + { + dd($request->input()); + } + + public function getView(Request $request, $uuid, $id) + { + $server = Models\Server::getByUUID($uuid); + $this->authorize('view-task', $server); + + return view('server.tasks.view', [ + 'server' => $server, + 'node' => Models\Node::findOrFail($server->node), + 'task' => Models\Task::where('id', $id)->where('server', $server->id)->firstOrFail() + ]); + } +} diff --git a/app/Http/Routes/ServerRoutes.php b/app/Http/Routes/ServerRoutes.php index 2614e6c55..9a0cd8ea3 100644 --- a/app/Http/Routes/ServerRoutes.php +++ b/app/Http/Routes/ServerRoutes.php @@ -117,6 +117,30 @@ class ServerRoutes { 'uses' => 'Server\SubuserController@deleteSubuser' ]); + $router->get('tasks/', [ + 'as' => 'server.tasks', + 'uses' => 'Server\TaskController@getIndex' + ]); + + $router->get('tasks/view/{id}', [ + 'as' => 'server.tasks.view', + 'uses' => 'Server\TaskController@getView' + ]); + + $router->get('tasks/new', [ + 'as' => 'server.tasks.new', + 'uses' => 'Server\TaskController@getNew' + ]); + + $router->post('tasks/new', [ + 'uses' => 'Server\TaskController@postNew' + ]); + + $router->delete('tasks/delete/{id}', [ + 'as' => 'server.tasks.delete', + 'uses' => 'Server\TaskController@deleteTask' + ]); + // Assorted AJAX Routes $router->group(['prefix' => 'ajax'], function ($server) use ($router) { // Returns Server Status diff --git a/app/Policies/ServerPolicy.php b/app/Policies/ServerPolicy.php index b6a80fb0f..05e66bc02 100644 --- a/app/Policies/ServerPolicy.php +++ b/app/Policies/ServerPolicy.php @@ -450,4 +450,100 @@ class ServerPolicy return $user->permissions()->server($server)->permission('view-databases')->exists(); } + /** + * Check if user has permission to view all tasks for a server. + * + * @param Pterodactyl\Models\User $user + * @param Pterodactyl\Models\Server $server + * @return boolean + */ + public function listTasks(User $user, Server $server) + { + if ($this->isOwner($user, $server)) { + return true; + } + + return $user->permissions()->server($server)->permission('list-tasks')->exists(); + } + + /** + * Check if user has permission to view a specific task for a server. + * + * @param Pterodactyl\Models\User $user + * @param Pterodactyl\Models\Server $server + * @return boolean + */ + public function viewTask(User $user, Server $server) + { + if ($this->isOwner($user, $server)) { + return true; + } + + return $user->permissions()->server($server)->permission('view-task')->exists(); + } + + /** + * Check if user has permission to view a toggle a task for a server. + * + * @param Pterodactyl\Models\User $user + * @param Pterodactyl\Models\Server $server + * @return boolean + */ + public function toggleTask(User $user, Server $server) + { + if ($this->isOwner($user, $server)) { + return true; + } + + return $user->permissions()->server($server)->permission('toggle-task')->exists(); + } + + /** + * Check if user has permission to queue a task for a server. + * + * @param Pterodactyl\Models\User $user + * @param Pterodactyl\Models\Server $server + * @return boolean + */ + public function queueTask(User $user, Server $server) + { + if ($this->isOwner($user, $server)) { + return true; + } + + return $user->permissions()->server($server)->permission('queue-task')->exists(); + } + + /** + * Check if user has permission to delete a specific task for a server. + * + * @param Pterodactyl\Models\User $user + * @param Pterodactyl\Models\Server $server + * @return boolean + */ + public function deleteTask(User $user, Server $server) + { + if ($this->isOwner($user, $server)) { + return true; + } + + return $user->permissions()->server($server)->permission('delete-task')->exists(); + } + + /** + * Check if user has permission to create a task for a server. + * + * @param Pterodactyl\Models\User $user + * @param Pterodactyl\Models\Server $server + * @return boolean + */ + public function createTask(User $user, Server $server) + { + if ($this->isOwner($user, $server)) { + return true; + } + + return $user->permissions()->server($server)->permission('create-task')->exists(); + } + } diff --git a/app/Repositories/SubuserRepository.php b/app/Repositories/SubuserRepository.php index 459a70a9d..e175ef0cf 100644 --- a/app/Repositories/SubuserRepository.php +++ b/app/Repositories/SubuserRepository.php @@ -79,6 +79,14 @@ class SubuserRepository 'create-subuser' => null, 'delete-subuser' => null, + // Tasks + 'list-tasks' => null, + 'view-task' => null, + 'toggle-task' => null, + 'delete-task' => null, + 'create-task' => null, + 'queue-task' => null, + // Management 'set-connection' => null, 'view-startup' => null, diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php new file mode 100644 index 000000000..3493ba272 --- /dev/null +++ b/app/Repositories/TaskRepository.php @@ -0,0 +1,130 @@ + + * + * 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; + +use Cron; +use Validator; + +use Pterodactyl\Models; + +use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Exceptions\DisplayException; + +class TaskRepository +{ + + protected $defaults = [ + 'year' => '*', + 'day_of_week' => '*', + 'month' => '*', + 'day_of_month' => '*', + 'hour' => '*', + 'minute' => '*/30', + ]; + + protected $actions = [ + 'command', + 'power', + ]; + + public function __construct() + { + // + } + + /** + * Create a new scheduled task for a given server. + * @param int $id + * @param array $data + * + * @throws DisplayException + * @throws DisplayValidationException + * @return void + */ + public function create($id, $data) + { + $server = Models\Server::findOrFail($id); + + $validator = Validator::make($data, [ + 'action' => 'string|required', + 'data' => 'string|required', + 'year' => 'string|sometimes|required', + 'day_of_week' => 'string|sometimes|required', + 'month' => 'string|sometimes|required', + 'day_of_month' => 'string|sometimes|required', + 'hour' => 'string|sometimes|required', + 'minute' => 'string|sometimes|required' + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); + } + + if (!in_array($data['action'], $this->actions)) { + throw new DisplayException('The action provided is not valid.'); + } + + $cron = $this->defaults; + foreach ($this->defaults as $setting => $value) { + if (array_key_exists($setting, $data)) { + $cron[$setting] = $data[$setting]; + } + } + + // Check that is this a valid Cron Entry + try { + $buildCron = Cron::factory(sprintf('%s %s %s %s %s %s', + $cron['minute'], + $cron['hour'], + $cron['day_of_month'], + $cron['month'], + $cron['day_of_week'], + $cron['year'] + )); + } catch (\Exception $ex) { + throw new DisplayException($ex); + } + + $task = new Models\Task; + $task->fill([ + 'server' => $server->id, + 'active' => 1, + 'action' => $data['action'], + 'data' => $data['data'], + 'queued' => 0, + 'year' => $cron['year'], + 'day_of_week' => $cron['day_of_week'], + 'month' => $cron['month'], + 'day_of_month' => $cron['day_of_month'], + 'hour' => $cron['hour'], + 'minute' => $cron['minute'], + 'last_run' => null, + 'next_run' => $buildCron->getNextRunDate() + ]); + + return $task->save(); + + } + +} diff --git a/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php new file mode 100644 index 000000000..65a0b3d9c --- /dev/null +++ b/database/migrations/2016_03_18_155649_add_nullable_field_lastrun.php @@ -0,0 +1,29 @@ +wrapTable('tasks'); + DB::statement('ALTER TABLE '.$table.' CHANGE `last_run` `last_run` TIMESTAMP NULL;'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $table = DB::getQueryGrammar()->wrapTable('tasks'); + DB::statement('ALTER TABLE '.$table.' CHANGE `last_run` `last_run` TIMESTAMP;'); + } +} diff --git a/public/themes/default/css/pterodactyl.css b/public/themes/default/css/pterodactyl.css index 176c14d3a..04798f049 100755 --- a/public/themes/default/css/pterodactyl.css +++ b/public/themes/default/css/pterodactyl.css @@ -138,6 +138,11 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default. width: 100%; } +.fuelux .input-group-btn .dropdown-menu { + max-height:250px; + overflow-y:scroll; +} + .btn-allocate-delete { height:34px; width:34px; @@ -149,3 +154,16 @@ li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default. margin-top:22px; } } + +.text-disabled { + color: #999; + opacity: 0.8; +} + +.text-disabled code { + opacity: 0.6; +} + +.text-v-center { + vertical-align: middle !important; +} diff --git a/resources/views/layouts/master.blade.php b/resources/views/layouts/master.blade.php index 5f97bf11b..a23d28bc4 100644 --- a/resources/views/layouts/master.blade.php +++ b/resources/views/layouts/master.blade.php @@ -193,6 +193,7 @@
  • {{ trans('pagination.sidebar.overview') }}
  • @can('list-files', $server)
  • {{ trans('pagination.sidebar.files') }}
  • @endcan @can('list-subusers', $server)
  • {{ trans('pagination.sidebar.subusers') }}
  • @endcan + @can('list-tasks', $server)
  • Scheduled Tasks
  • @endcan @can('view-sftp', $server)
  • {{ trans('pagination.sidebar.manage') }}
  • @else @@ -249,6 +250,7 @@ {{ trans('pagination.sidebar.overview') }} @can('list-files', $server){{ trans('pagination.sidebar.files') }}@endcan @can('list-subusers', $server){{ trans('pagination.sidebar.subusers') }}@endcan + @can('list-tasks', $server)Scheduled Tasks@endcan @can('view-sftp', $server) {{ trans('pagination.sidebar.manage') }} @else diff --git a/resources/views/server/tasks/index.blade.php b/resources/views/server/tasks/index.blade.php new file mode 100644 index 000000000..aa044c1b0 --- /dev/null +++ b/resources/views/server/tasks/index.blade.php @@ -0,0 +1,73 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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. --}} +@extends('layouts.master') + +@section('title') + Scheduled Tasks +@endsection + +@section('content') +
    +

    Manage Scheduled Tasks


    + + + + + + + + @can('delete-task', $server)@endcan + @can('toggle-task', $server)@endcan + + + + @foreach($tasks as $task) + active === 0)class="text-disabled"@endif> + + + + + @can('delete-task', $server) + + @endcan + @can('toggle-task', $server) + + @endcan + + + @endforeach + +
    ActionDataLast RunNext Run
    {{ $actions[$task->action] }}{{ $task->data }}{{ Carbon::parse($task->last_run)->toDayDateTimeString() }}

    ({{ Carbon::parse($task->last_run)->diffForHumans() }})

    + @if($task->active !== 0) + {{ Carbon::parse($task->next_run)->toDayDateTimeString() }}

    ({{ Carbon::parse($task->next_run)->diffForHumans() }})

    + @else + n/a + @endif +
    + @can('create-task', $server) + + @endcan +
    + +@endsection diff --git a/resources/views/server/tasks/new.blade.php b/resources/views/server/tasks/new.blade.php new file mode 100644 index 000000000..230a8399f --- /dev/null +++ b/resources/views/server/tasks/new.blade.php @@ -0,0 +1,172 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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. --}} +@extends('layouts.master') + +@section('title') + Scheduled Tasks +@endsection + +@section('content') +
    +

    Create Scheduled Task
    Current System Time: {{ Carbon::now()->toDayDateTimeString() }}


    +
    +
    You may use either the dropdown selection boxes or enter custom cron variables into the fields below.
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +

    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).

    +
    +
    +
    +
    +
    + {!! csrf_field() !!} + +
    +
    +
    +
    + +@endsection diff --git a/resources/views/server/tasks/view.blade.php b/resources/views/server/tasks/view.blade.php new file mode 100644 index 000000000..e69de29bb