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 @@
Action | +Data | +Last Run | +Next Run | + @can('delete-task', $server)@endcan + @can('toggle-task', $server) | @endcan + |
---|---|---|---|---|---|
{{ $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('delete-task', $server)
+ + @endcan + @can('toggle-task', $server) + | + @endcan + + |