From d7682bb7c91ff73b8d42b54ae12bd40bd02d8442 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 12 Mar 2017 00:00:06 -0500 Subject: [PATCH] Complete new service, option, and variable management interface in Admin CP --- .../Controllers/Admin/OptionController.php | 126 +++- .../Controllers/Admin/ServiceController.php | 41 +- app/Http/Routes/AdminRoutes.php | 11 +- app/Models/Service.php | 2 +- app/Repositories/OptionRepository.php | 84 +++ app/Repositories/ServiceRepository.php | 21 +- app/Repositories/VariableRepository.php | 140 ++-- config/pterodactyl.php | 1 + ..._ChangeServiceVariablesValidationRules.php | 47 ++ .../admin/services/options/new.blade.php | 170 +++++ .../services/options/variables.blade.php | 114 +++ .../admin/services/options/view.blade.php | 14 +- .../pterodactyl/admin/services/view.blade.php | 12 + resources/views/admin/servers/view.blade.php | 687 ------------------ .../admin/services/options/variable.blade.php | 122 ---- storage/app/services/index.js | 31 + 16 files changed, 698 insertions(+), 925 deletions(-) create mode 100644 database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php create mode 100644 resources/themes/pterodactyl/admin/services/options/new.blade.php create mode 100644 resources/themes/pterodactyl/admin/services/options/variables.blade.php delete mode 100644 resources/views/admin/servers/view.blade.php delete mode 100644 resources/views/admin/services/options/variable.blade.php create mode 100644 storage/app/services/index.js diff --git a/app/Http/Controllers/Admin/OptionController.php b/app/Http/Controllers/Admin/OptionController.php index 3dc9b24f..b60c0df7 100644 --- a/app/Http/Controllers/Admin/OptionController.php +++ b/app/Http/Controllers/Admin/OptionController.php @@ -27,15 +27,63 @@ namespace Pterodactyl\Http\Controllers\Admin; use Log; use Alert; use Storage; -use Pterodactyl\Models; +use Javascript; use Illuminate\Http\Request; +use Pterodactyl\Models\Service; +use Pterodactyl\Models\ServiceOption; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\OptionRepository; +use Pterodactyl\Repositories\VariableRepository; use Pterodactyl\Exceptions\DisplayValidationException; class OptionController extends Controller { + /** + * Handles request to view page for adding new option. + * + * @param Request $request + * @return \Illuminate\View\View + */ + public function new(Request $request) + { + $services = Service::with('options')->get(); + Javascript::put(['services' => $services->keyBy('id')]); + + return view('admin.services.options.new', ['services' => $services]); + } + + /** + * Handles POST request to create a new option. + + * @param Request $request + * @return \Illuminate\Response\RedirectResponse + */ + public function create(Request $request) + { + $repo = new OptionRepository; + + try { + $option = $repo->create($request->intersect([ + 'service_id', 'name', 'description', 'tag', + 'docker_image', 'startup', 'config_from', 'config_startup', + 'config_logs', 'config_files', 'config_stop' + ])); + Alert::success('Successfully created new service option.')->flash(); + + return redirect()->route('admin.services.option.view', $option->id); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.new')->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception occurred while attempting to create this service. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.new')->withInput(); + } + /** * Display option overview page. * @@ -45,27 +93,89 @@ class OptionController extends Controller */ public function viewConfiguration(Request $request, $id) { - return view('admin.services.options.view', ['option' => Models\ServiceOption::findOrFail($id)]); + return view('admin.services.options.view', ['option' => ServiceOption::findOrFail($id)]); } + /** + * Display variable overview page for a service option. + * + * @param Request $request + * @param int $id + * @return \Illuminate\View\View + */ + public function viewVariables(Request $request, $id) + { + return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]); + } + + /** + * Handles POST when editing a configration for a service option. + * + * @param Request $request + * @return \Illuminate\Response\RedirectResponse + */ public function editConfiguration(Request $request, $id) { $repo = new OptionRepository; try { - $repo->update($id, $request->intersect([ - 'name', 'description', 'tag', 'docker_image', 'startup', - 'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup', - ])); + if ($request->input('action') !== 'delete') { + $repo->update($id, $request->intersect([ + 'name', 'description', 'tag', 'docker_image', 'startup', + 'config_from', 'config_stop', 'config_logs', 'config_files', 'config_startup', + ])); + Alert::success('Service option configuration has been successfully updated.')->flash(); + } else { + $option = ServiceOption::with('service')->where('id', $id)->first(); + $repo->delete($id); + Alert::success('Successfully deleted service option from the system.')->flash(); - Alert::success('Service option configuration has been successfully updated.')->flash(); + return redirect()->route('admin.services.view', $option->service_id); + } } catch (DisplayValidationException $ex) { return redirect()->route('admin.services.option.view', $id)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); } catch (\Exception $ex) { Log::error($ex); - Alert::danger('An unhandled exception occurred while attempting to update this service option. This error has been logged.')->flash(); + Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash(); } return redirect()->route('admin.services.option.view', $id); } + + /** + * Handles POST when editing a configration for a service option. + * + * @param Request $request + * @param int $option + * @param int $variable + * @return \Illuminate\Response\RedirectResponse + */ + public function editVariable(Request $request, $option, $variable) + { + $repo = new VariableRepository; + + try { + if ($request->input('action') !== 'delete') { + $variable = $repo->update($variable, $request->only([ + 'name', 'description', 'env_variable', + 'default_value', 'options', 'rules', + ])); + Alert::success("The service variable '{$variable->name}' has been updated.")->flash(); + } else { + $repo->delete($variable); + Alert::success("That service variable has been deleted.")->flash(); + } + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.option.variables', $option)->withErrors(json_decode($ex->getMessage())); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash(); + } + + return redirect()->route('admin.services.option.variables', $option); + } } diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index 81ca528b..60450ab2 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -103,32 +103,6 @@ class ServiceController extends Controller return redirect()->route('admin.services.new')->withInput(); } - /** - * Delete a service from the system. - * - * @param Request $request - * @param int $id - * @return \Illuminate\Response\RedirectResponse - */ - public function delete(Request $request, $id) - { - $repo = new ServiceRepository; - - try { - $repo->delete($id); - Alert::success('Successfully deleted service.')->flash(); - - return redirect()->route('admin.services'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An error was encountered while attempting to delete that service. This error has been logged')->flash(); - } - - return redirect()->route('admin.services.view', $id); - } - /** * Edits configuration for a specific service. * @@ -141,10 +115,17 @@ class ServiceController extends Controller $repo = new ServiceRepository; try { - $repo->update($id, $request->intersect([ - 'name', 'description', 'folder', 'startup', - ])); - Alert::success('Service has been updated successfully.')->flash(); + if ($request->input('action') !== 'delete') { + $repo->update($id, $request->intersect([ + 'name', 'description', 'folder', 'startup', + ])); + Alert::success('Service has been updated successfully.')->flash(); + } else { + $repo->delete($id); + Alert::success('Successfully deleted service from the system.')->flash(); + + return redirect()->route('admin.services'); + } } catch (DisplayValidationException $ex) { return redirect()->route('admin.services.view', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); } catch (DisplayException $ex) { diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index edf8fdc9..5cec94b5 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -419,20 +419,25 @@ class AdminRoutes 'uses' => 'Admin\OptionController@new', ]); + $router->post('/option/new', 'Admin\OptionController@create'); + $router->get('/option/{id}', [ 'as' => 'admin.services.option.view', 'uses' => 'Admin\OptionController@viewConfiguration', ]); $router->get('/option/{id}/variables', [ - 'as' => 'admin.services.option.view.variables', + 'as' => 'admin.services.option.variables', 'uses' => 'Admin\OptionController@viewVariables', ]); - $router->post('/option/{id}', [ - 'uses' => 'Admin\OptionController@editConfiguration', + $router->post('/option/{id}/variables/{variable}', [ + 'as' => 'admin.services.option.variables.edit', + 'uses' => 'Admin\OptionController@editVariable', ]); + $router->post('/option/{id}', 'Admin\OptionController@editConfiguration'); + }); // Service Packs diff --git a/app/Models/Service.php b/app/Models/Service.php index 83cdb70b..50679633 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -41,7 +41,7 @@ class Service extends Model * @var array */ protected $fillable = [ - 'name', 'description', 'file', 'executable', 'startup', + 'name', 'description', 'folder', 'startup', ]; /** diff --git a/app/Repositories/OptionRepository.php b/app/Repositories/OptionRepository.php index 7ded0968..e33eb652 100644 --- a/app/Repositories/OptionRepository.php +++ b/app/Repositories/OptionRepository.php @@ -28,10 +28,74 @@ use DB; use Validator; use Pterodactyl\Models\ServiceOption; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\VariableRepository; use Pterodactyl\Exceptions\DisplayValidationException; class OptionRepository { + /** + * Creates a new service option on the system. + * + * @param array $data + * @return \Pterodactyl\Models\ServiceOption + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + public function create(array $data) + { + $validator = Validator::make($data, [ + 'service_id' => 'required|numeric|exists:services,id', + 'name' => 'required|string|max:255', + 'description' => 'required|string', + 'tag' => 'required|string|max:255|unique:service_options,tag', + 'docker_image' => 'required|string|max:255', + 'startup' => 'required|string', + 'config_from' => 'sometimes|required|numeric|exists:service_options,id', + 'config_startup' => 'required_without:config_from|json', + 'config_stop' => 'required_without:config_from|string|max:255', + 'config_logs' => 'required_without:config_from|json', + 'config_files' => 'required_without:config_from|json', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + if (isset($data['config_from'])) { + if (! ServiceOption::where('service_id', $data['service_id'])->where('id', $data['config_from'])->first()) { + throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); + } + } + + return ServiceOption::create($data); + } + + /** + * Deletes a service option from the system. + * + * @param int $id + * @return void + * + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function delete($id) + { + $option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id); + + if ($option->servers_count > 0) { + throw new DisplayException('You cannot delete a service option that has servers associated with it.'); + } + + DB::transaction(function () use ($option) { + foreach($option->variables as $variable) { + (new VariableRepository)->delete($variable->id); + } + + $option->delete(); + }); + } + /** * Updates a service option in the database which can then be used * on nodes. @@ -39,11 +103,25 @@ class OptionRepository * @param int $id * @param array $data * @return \Pterodactyl\Models\ServiceOption + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException */ public function update($id, array $data) { $option = ServiceOption::findOrFail($id); + // Due to code limitations (at least when I am writing this currently) + // we have to make an assumption that if config_from is not passed + // that we should be telling it that no config is wanted anymore. + // + // This really is only an issue if we open API access to this function, + // in which case users will always need to pass `config_from` in order + // to keep it assigned. + if (! isset($data['config_from']) && ! is_null($option->config_from)) { + $option->config_from = null; + } + $validator = Validator::make($data, [ 'name' => 'sometimes|required|string|max:255', 'description' => 'sometimes|required|string', @@ -73,6 +151,12 @@ class OptionRepository throw new DisplayValidationException($validator->errors()); } + if (isset($data['config_from'])) { + if (! ServiceOption::where('service_id', $option->service_id)->where('id', $data['config_from'])->first()) { + throw new DisplayException('The `configuration from` directive must be a child of the assigned service.'); + } + } + $option->fill($data)->save(); return $option; diff --git a/app/Repositories/ServiceRepository.php b/app/Repositories/ServiceRepository.php index ed9dbf03..a82c80b3 100644 --- a/app/Repositories/ServiceRepository.php +++ b/app/Repositories/ServiceRepository.php @@ -31,6 +31,7 @@ use Validator; use Pterodactyl\Models\Service; use Pterodactyl\Models\ServiceVariable; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\OptionRepository; use Pterodactyl\Exceptions\DisplayValidationException; class ServiceRepository @@ -55,13 +56,14 @@ class ServiceRepository } $service = DB::transaction(function () use ($data) { - $service = Service::create([ - 'author' => config('pterodactyl.service.author'), + $service = new Service; + $service->author = config('pterodactyl.service.author'); + $service->fill([ 'name' => $data['name'], 'description' => (isset($data['description'])) ? $data['description'] : null, 'folder' => $data['folder'], 'startup' => (isset($data['startup'])) ? $data['startup'] : null, - ]); + ])->save(); // It is possible for an event to return false or throw an exception // which won't necessarily be detected by this transaction. @@ -105,8 +107,7 @@ class ServiceRepository $moveFiles = (isset($data['folder']) && $data['folder'] !== $service->folder); $oldFolder = $service->folder; - $service->fill($data); - $service->save(); + $service->fill($data)->save(); if ($moveFiles) { Storage::move(sprintf('services/%s/index.js', $oldFolder), sprintf('services/%s/index.js', $service->folder)); @@ -124,18 +125,16 @@ class ServiceRepository */ public function delete($id) { - $service = Service::withCount('servers', 'options')->findOrFail($id); + $service = Service::withCount('servers')->with('options')->findOrFail($id); if ($service->servers_count > 0) { throw new DisplayException('You cannot delete a service that has servers associated with it.'); } DB::transaction(function () use ($service) { - ServiceVariable::whereIn('option_id', $service->options->pluck('id')->all())->delete(); - - $service->options->each(function ($item) { - $item->delete(); - }); + foreach($service->options as $option) { + (new OptionRepository)->delete($option->id); + } $service->delete(); Storage::deleteDirectory('services/' . $service->folder); diff --git a/app/Repositories/VariableRepository.php b/app/Repositories/VariableRepository.php index 744cb8bc..b9421897 100644 --- a/app/Repositories/VariableRepository.php +++ b/app/Repositories/VariableRepository.php @@ -26,7 +26,7 @@ namespace Pterodactyl\Repositories; use DB; use Validator; -use Pterodactyl\Models; +use Pterodactyl\Models\ServiceVariable; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayValidationException; @@ -37,108 +37,126 @@ class VariableRepository // } - public function create($id, array $data) + public function create(array $data) { - $option = Models\ServiceOption::select('id')->findOrFail($id); - $validator = Validator::make($data, [ + 'option_id' => 'required|numeric|exists:service_options,id', 'name' => 'required|string|min:1|max:255', - 'description' => 'required|string', + 'description' => 'sometimes|nullable|string', 'env_variable' => 'required|regex:/^[\w]{1,255}$/', - 'default_value' => 'string|max:255', - 'user_viewable' => 'sometimes|required|nullable|boolean', - 'user_editable' => 'sometimes|required|nullable|boolean', - 'required' => 'sometimes|required|nullable|boolean', - 'regex' => 'required|string|min:1', + 'default_value' => 'string', + 'options' => 'sometimes|required|array', + 'rules' => 'bail|required|string|min:1', ]); + // Ensure the default value is allowed by the rules provided. + $rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules; + $validator->sometimes('default_value', $rules, function ($input) { + return $input->default_value; + }); + if ($validator->fails()) { throw new DisplayValidationException($validator->errors()); } - if ($data['default_value'] !== '' && ! preg_match($data['regex'], $data['default_value'])) { - throw new DisplayException('The default value you entered cannot violate the regex requirements.'); + if (isset($data['env_variable'])) { + $search = ServiceVariable::where('env_variable', $data['env_variable']) + ->where('option_id', $variable->option_id) + ->where('id', '!=', $variable->id); + if ($search->first()) { + throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); + } } - if (Models\ServiceVariable::where('env_variable', $data['env_variable'])->where('option_id', $option->id)->first()) { - throw new DisplayException('An environment variable with that name already exists for this option.'); + if (! isset($data['options']) || ! is_array($data['options'])) { + $data['options'] = []; } - $data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : 0; - $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : 0; - $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : 0; - $data['option_id'] = $option->id; + $data['user_viewable'] = (in_array('user_viewable', $data['options'])); + $data['user_editable'] = (in_array('user_editable', $data['options'])); + $data['required'] = (in_array('required', $data['options'])); - $variable = Models\ServiceVariable::create($data); + // Remove field that isn't used. + unset($data['options']); - return $variable; + return ServiceVariable::create($data); } + /** + * Deletes a specified option variable as well as all server + * variables currently assigned. + * + * @param int $id + * @return void + */ public function delete($id) { - $variable = Models\ServiceVariable::with('serverVariable')->findOrFail($id); + $variable = ServiceVariable::with('serverVariable')->findOrFail($id); - DB::beginTransaction(); - try { - foreach ($variable->serverVariable as $svar) { - $svar->delete(); + DB::transaction(function () use ($variable) { + foreach ($variable->serverVariable as $v) { + $v->delete(); } - $variable->delete(); - DB::commit(); - } catch (\Exception $ex) { - DB::rollBack(); - throw $ex; - } + $variable->delete(); + }); } + /** + * Updates a given service variable. + * + * @param int $id + * @param array $data + * @return \Pterodactyl\Models\ServiceVariable + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ public function update($id, array $data) { - $variable = Models\ServiceVariable::findOrFail($id); + $variable = ServiceVariable::findOrFail($id); $validator = Validator::make($data, [ 'name' => 'sometimes|required|string|min:1|max:255', - 'description' => 'sometimes|required|string', + 'description' => 'sometimes|nullable|string', 'env_variable' => 'sometimes|required|regex:/^[\w]{1,255}$/', - 'default_value' => 'sometimes|string|max:255', - 'user_viewable' => 'sometimes|required|nullable|boolean', - 'user_editable' => 'sometimes|required|nullable|boolean', - 'required' => 'sometimes|required|nullable|boolean', - 'regex' => 'sometimes|required|string|min:1', + 'default_value' => 'string', + 'options' => 'sometimes|required|array', + 'rules' => 'bail|sometimes|required|string|min:1', ]); + // Ensure the default value is allowed by the rules provided. + $rules = (isset($data['rules'])) ? $data['rules'] : $variable->rules; + $validator->sometimes('default_value', $rules, function ($input) { + return $input->default_value; + }); + if ($validator->fails()) { throw new DisplayValidationException($validator->errors()); } - $data['default_value'] = (isset($data['default_value'])) ? $data['default_value'] : $variable->default_value; - $data['regex'] = (isset($data['regex'])) ? $data['regex'] : $variable->regex; - - if ($data['default_value'] !== '' && ! preg_match($data['regex'], $data['default_value'])) { - throw new DisplayException('The default value you entered cannot violate the regex requirements.'); + if (isset($data['env_variable'])) { + $search = ServiceVariable::where('env_variable', $data['env_variable']) + ->where('option_id', $variable->option_id) + ->where('id', '!=', $variable->id); + if ($search->first()) { + throw new DisplayException('The envionment variable name assigned to this variable must be unique for this service option.'); + } } - if (Models\ServiceVariable::where('id', '!=', $variable->id)->where('env_variable', $data['env_variable'])->where('option_id', $variable->option_id)->first()) { - throw new DisplayException('An environment variable with that name already exists for this option.'); + if (! isset($data['options']) || ! is_array($data['options'])) { + $data['options'] = []; } - $data['user_viewable'] = (isset($data['user_viewable']) && in_array((int) $data['user_viewable'], [0, 1])) ? $data['user_viewable'] : $variable->user_viewable; - $data['user_editable'] = (isset($data['user_editable']) && in_array((int) $data['user_editable'], [0, 1])) ? $data['user_editable'] : $variable->user_editable; - $data['required'] = (isset($data['required']) && in_array((int) $data['required'], [0, 1])) ? $data['required'] : $variable->required; + $data['user_viewable'] = (in_array('user_viewable', $data['options'])); + $data['user_editable'] = (in_array('user_editable', $data['options'])); + $data['required'] = (in_array('required', $data['options'])); - // Not using $data because the function that passes into this function - // can't do $requst->only() due to the page setup. - $variable->fill([ - 'name' => $data['name'], - 'description' => $data['description'], - 'env_variable' => $data['env_variable'], - 'default_value' => $data['default_value'], - 'user_viewable' => $data['user_viewable'], - 'user_editable' => $data['user_editable'], - 'required' => $data['required'], - 'regex' => $data['regex'], - ]); + // Remove field that isn't used. + unset($data['options']); - return $variable->save(); + $variable->fill($data)->save(); + + return $variable; } } diff --git a/config/pterodactyl.php b/config/pterodactyl.php index ec0e430c..30ea7ef9 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -12,6 +12,7 @@ return [ | standard Pterodactyl shipped services. */ 'service' => [ + 'core' => 'ptrdctyl-v040-11e6-8b77-86f30ca893d3', 'author' => env('SERVICE_AUTHOR'), ], diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php new file mode 100644 index 00000000..1ade5b53 --- /dev/null +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -0,0 +1,47 @@ +renameColumn('regex', 'rules'); + }); + + DB::transaction(function () { + foreach(ServiceVariable::all() as $variable) { + $variable->rules = ($variable->required) ? 'required|regex:' . $variable->rules : 'regex:' . $variable->regex; + $variable->save(); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('service_variables', function (Blueprint $table) { + $table->renameColumn('rules', 'regex'); + }); + + DB::transaction(function () { + foreach(ServiceVariable::all() as $variable) { + $variable->regex = str_replace(['required|regex:', 'regex:'], '', $variable->regex); + $variable->save(); + } + }); + } +} diff --git a/resources/themes/pterodactyl/admin/services/options/new.blade.php b/resources/themes/pterodactyl/admin/services/options/new.blade.php new file mode 100644 index 00000000..763e6096 --- /dev/null +++ b/resources/themes/pterodactyl/admin/services/options/new.blade.php @@ -0,0 +1,170 @@ +{{-- Copyright (c) 2015 - 2017 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.admin') + +@section('title') + Services → New Option +@endsection + +@section('content-header') +

New OptionCreate a new service option to assign to servers.

+ +@endsection + +@section('content') +
+
+
+
+
+

Configuration

+
+
+
+
+
+ + +
+
+ + +

A simple, human-readable name to use as an identifier for this service.

+
+
+ + +

A description of this service that will be displayed throughout the panel as needed.

+
+
+
+
+ + +

This should be a unique identifer for this service option that is not used for any other service options.

+
+
+ + +

The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.

+
+
+ + +

The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.

+
+
+
+
+
+
+
+
+
+

Process Management

+
+
+
+
+
+

All fields are required unless you select a seperate option from the 'Copy Settings From' dropdown, in which case fields may be left blank to use the values from that option.

+
+
+
+
+ + +

If you would like to default to settings from another option select the option from the menu above.

+
+
+ + +

The command that should be sent to server processes to stop them gracefully. If you need to send a SIGINT you should enter ^C here.

+
+
+ + +

This should be a JSON representation of where log files are stored, and wether or not the daemon should be creating custom logs.

+
+
+
+
+ + +

This should be a JSON representation of configuration files to modify and what parts should be changed.

+
+
+ + +

This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.

+
+
+
+ +
+
+
+ +@endsection + +@section('footer-scripts') + @parent + {!! Theme::js('vendor/lodash/lodash.js') !!} + +@endsection diff --git a/resources/themes/pterodactyl/admin/services/options/variables.blade.php b/resources/themes/pterodactyl/admin/services/options/variables.blade.php new file mode 100644 index 00000000..c1870d3e --- /dev/null +++ b/resources/themes/pterodactyl/admin/services/options/variables.blade.php @@ -0,0 +1,114 @@ +{{-- Copyright (c) 2015 - 2017 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.admin') + +@section('title') + Service Options → {{ $option->name }} → Variables +@endsection + +@section('content-header') +

{{ $option->name }}Managing variables for this service option.

+ +@endsection + +@section('content') +
+
+ +
+
+
+ @foreach($option->variables as $variable) +
+
+
+

{{ $variable->name }}

+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+

This variable can be accessed in the statup command by using {{ $variable->env_variable }}.

+
+
+
+ + +
+
+ + +

These rules are defined using standard Laravel Framework validation rules.

+
+
+ +
+
+
+ @endforeach +
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/services/options/view.blade.php b/resources/themes/pterodactyl/admin/services/options/view.blade.php index 01965d4e..6450fdef 100644 --- a/resources/themes/pterodactyl/admin/services/options/view.blade.php +++ b/resources/themes/pterodactyl/admin/services/options/view.blade.php @@ -39,7 +39,7 @@
@@ -131,12 +131,16 @@

This should be a JSON representation of what values the daemon should be looking for when booting a server to determine completion.

+ @@ -147,6 +151,12 @@ @section('footer-scripts') @parent +@endsection diff --git a/resources/views/admin/servers/view.blade.php b/resources/views/admin/servers/view.blade.php deleted file mode 100644 index 3f65dbd1..00000000 --- a/resources/views/admin/servers/view.blade.php +++ /dev/null @@ -1,687 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 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.admin') - -@section('title') - Managing Server: {{ $server->name }} ({{ $server->uuidShort}}) -@endsection - -@section('content') -
- - @if($server->suspended === 1 && !$server->trashed()) -
- This server is suspended and has no user access. Processes cannot be started and files cannot be modified. All API access is disabled unless using a master token. -
- @elseif($server->trashed()) -
- This server is marked for deletion {{ Carbon::parse($server->deleted_at)->addMinutes(env('APP_DELETE_MINUTES', 10))->diffForHumans() }}. If you want to cancel this action simply click the button below. -

-
- - - - {!! csrf_field() !!} -
-
- @endif - @if($server->installed === 0) -
- This server is still running through the install process and is not avaliable for use just yet. This message will disappear once this process is completed. -
- @elseif($server->installed === 2) -
- This server failed to install properly. You should delete it and try to create it again or check the daemon logs. -
- @endif - -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UUID{{ $server->uuid }}
Docker Container ID
Docker User ID
Owner{{ $server->user->email }}
Location{{ $server->node->location->short }}
Node{{ $server->node->name }}
Service{{ $server->option->service->name }} :: {{ $server->option->name }}
Name{{ $server->name }}
Memory{{ $server->memory }}MB / {{ $server->swap }}MB
OOM Killer{!! ($server->oom_disabled === 0) ? 'Enabled' : 'Disabled' !!}
Disk Space{{ $server->disk }}MB
Block IO Weight{{ $server->io }}
CPU Limit{{ $server->cpu }}%
Default Connection{{ $server->allocation->ip }}:{{ $server->allocation->port }}
Connection Alias - @if($server->allocation->alias !== $server->allocation->ip) - {{ $server->allocation->alias }}:{{ $server->allocation->port }} - @else - No Alias Assigned - @endif -
Installed{!! ($server->installed === 1) ? 'Yes' : 'No' !!}
Suspended{!! ($server->suspended === 1) ? 'Suspended' : 'No' !!}
-
-
-
- @if($server->installed === 1) -
-
-
-
-
-
- -
- -

Character limits: a-zA-Z0-9_- and [Space] (max 35 characters).

-
-
-
- -
- -

You can change the owner of this server by changing this field to an email matching another use on this system. If you do this a new daemon security token will be generated automatically.

-
-
-
- -
- -

This token should not be shared with anyone as it has full control over this server.

-
-
-
-
- Yes, Reset Daemon Token -

Resetting this token will cause any requests using the old token to fail.

-
-
-
- {!! csrf_field() !!} - -
-
-
-
-
-
-
-
-
- -
- -

The docker image to use for this server. The default image for this service and option combination is {{ $server->docker_image }}.

-
-
-
-
-
- -
- {!! csrf_field() !!} - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- After editing any of the options below you will need to restart the server for changes to take effect. If the server is currently off, you just need to start it and the container will be rebuilt with the new settings. -
-
-
-
-
-
- -
- - MB -
-
-
- -
- - MB -
-

Setting this to 0 will disable swap space on this server.

-
-
-
-
- -
- - % -
-

Each physical core on the system is considered to be 100%. Setting this value to 0 will allow a server to use CPU time without restrictions.

-
-
- -
- -
-

Changing this value can have negative effects on all containers on the system. We strongly recommend leaving this value as 500.

-
-
-
-
-
-
- Additional IPs and Ports can be assigned to this server for use by plugins or other software. The game port is what will show up for the user to use to connect to thier server, and what their configuration files will be forced to use for binding. -
-
-
- - @foreach ($assigned as $assignment) -
- - id === $server->allocation_id) checked="checked" @endif - name="default" value="{{ $assignment->ip }}:{{ $assignment->port }}"/> - - has_alias) - data-toggle="tooltip" data-placement="left" title="{{ $assignment->ip }}:{{ $assignment->port }}" - @endif - /> -
- @endforeach -
-
-
-
- -
- -
-

Please note that due to software limitations you cannot assign identical ports on different IPs to the same server. For example, you cannot assign both 192.168.0.5:25565 and 192.168.10.5:25565 to the same server.

-
-
-
-
- -
- -
-

Simply select which ports you would like to remove from the list above. If you want to assign a port on a different IP that is already in use you can select it above and delete it down here.

-
-
-
-
-
-
- {!! csrf_field() !!} - -
-
-
-
-
-
-
-
-
-
-
-
-
-
Changing any of the values below will require a restart for them to take effect.
- -
- {{ $server->option->display_executable }} - -
-

The following data replacers are avaliable for the startup command: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}. They will be replaced with the allocated memory, server ip, and server port respectively.

-
-
-
-
-
-
- @foreach($server->option->variables as $variable) -
- -
- -
-

{!! $variable->description !!}
Regex: {{ $variable->regex }}
Access as: {{{{ $variable->env_variable }}}}

-
- @endforeach -
-
-
-
-
-
- {!! csrf_field() !!} - -
-
-
-
-
-
-
-
-
-
-

Add New Database

-
-
-
- -
-
s{{ $server->id }}_
- -
-
-
- -
- -
-

Which IP to allow connections from. Standard MySQL wildcard notation allowed (e.g. 192.168.%.%).

-
-
-
-
- - -
-
- -
- {!! csrf_field() !!} - -
-
-
-
-
- @if(count($server->databases) > 0) -
-
- - - - - - - - - - - - @foreach($server->databases as $database) - - - - - - - - @endforeach - -
DatabaseUser (Connections From)PasswordDB Server
{{ $database->database }}{{ $database->username }} ({{ $database->remote }}){{ Crypt::decrypt($database->password) }} {{ $database->host->host }}:{{ $database->host->port }}
-
- @endif -
-
- @endif - @if($server->installed !== 2) -
-
-
-
-
- -
-

This will take you to the server management page that users normally see and allow you to manage server files as well as check the console and data usage.

-
-
-
-
-
-
-
-
- {!! csrf_field() !!} - -
-
-
-

This will toggle the install status for the server.

-
If you have just created this server it is ill advised to perform this action as the daemon will contact the panel when finished which could cause the install status to be wrongly set.
-
-
-
- @if($server->installed === 1) -
-
-
-
-
- {!! csrf_field() !!} - -
-
-
-

This will trigger a rebuild of the server container when it next starts up. This is useful if you modified the server configuration file manually, or something just didn't work out correctly.

-
A rebuild will automatically occur whenever you edit build configuration settings for the server.
-
-
-
- @endif -
-
-
- @if($server->suspended === 0) -
-
- {!! csrf_field() !!} - -
-
-
-

This will suspend the server, stop any running processes, and immediately block the user from being able to access their files or otherwise manage the server through the panel or API.

-
- @else -
-
- {!! csrf_field() !!} - -
-
-
-

This will unsuspend the server and restore normal user access.

-
- @endif -
-
-
-
- @endif -
-
-
- @if($server->installed === 1) -
-
-
-
- {!! csrf_field() !!} - {!! method_field('DELETE') !!} - -
-
-
-
Deleting a server is an irreversible action. All data will be immediately removed relating to this server.
-
-
-
-
- @endif -
-
-
-
- {!! csrf_field() !!} - {!! method_field('DELETE') !!} - -
-
-
-
This is the same as deleting a server, however, if an error is returned by the daemon it is ignored and the server is still removed from the panel.
-
-
-
-
-
-
-
- -@endsection diff --git a/resources/views/admin/services/options/variable.blade.php b/resources/views/admin/services/options/variable.blade.php deleted file mode 100644 index 5ed9540c..00000000 --- a/resources/views/admin/services/options/variable.blade.php +++ /dev/null @@ -1,122 +0,0 @@ -{{-- Copyright (c) 2015 - 2017 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.admin') - -@section('title') - New Variable for {{ $option->name }} -@endsection - -@section('content') -
- -

New Option Variable


-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -

Regex code to use when verifying the contents of the field.

-
-
-
-
-
- -
- -

Accessed in startup by using {{}} parameter.

-
-
-
- -
- -

The default value to use for this field.

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!! csrf_field() !!} - -
-
-
-
-
- -@endsection diff --git a/storage/app/services/index.js b/storage/app/services/index.js new file mode 100644 index 00000000..f6b6695d --- /dev/null +++ b/storage/app/services/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2017 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. + */ +const rfr = require('rfr'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service;