diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 509656952..64b30e163 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -198,7 +198,24 @@ class ServersController extends Controller return $item; }); - return view('admin.servers.view.startup', ['server' => $server]); + $services = Models\Service::with('options.packs', 'options.variables')->get(); + Javascript::put([ + 'services' => $services->map(function ($item) { + return array_merge($item->toArray(), [ + 'options' => $item->options->keyBy('id')->toArray(), + ]); + })->keyBy('id'), + 'server_variables' => $server->variables->mapWithKeys(function ($item) { + return ['env_' . $item->variable_id => [ + 'value' => $item->variable_value, + ]]; + })->toArray(), + ]); + + return view('admin.servers.view.startup', [ + 'server' => $server, + 'services' => $services, + ]); } /** @@ -479,9 +496,13 @@ class ServersController extends Controller $repo = new ServerRepository; try { - $repo->updateStartup($id, $request->except('_token'), true); + if ($repo->updateStartup($id, $request->except('_token'), true)) { + Alert::success('Service configuration successfully modfied for this server, reinstalling now.')->flash(); - Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); + return redirect()->route('admin.servers.view', $id); + } else { + Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); + } } catch (DisplayValidationException $ex) { return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage())); } catch (DisplayException $ex) { diff --git a/app/Models/Server.php b/app/Models/Server.php index 729ae4f0d..8efad3845 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -72,6 +72,7 @@ class Server extends Model */ protected $casts = [ 'node_id' => 'integer', + 'skip_scripts' => 'boolean', 'suspended' => 'integer', 'owner_id' => 'integer', 'memory' => 'integer', diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 41b9a2a82..6aac53a0c 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -103,7 +103,7 @@ class ServerRepository 'startup' => 'string', 'auto_deploy' => 'sometimes|required|accepted', 'custom_id' => 'sometimes|required|numeric|unique:servers,id', - 'skip_scripting' => 'sometimes|required|boolean', + 'skip_scripts' => 'sometimes|required|boolean', ]); $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) { @@ -250,14 +250,15 @@ class ServerRepository 'node_id' => $node->id, 'name' => $data['name'], 'description' => $data['description'], - 'suspended' => 0, + 'skip_scripts' => isset($data['skip_scripts']), + 'suspended' => false, 'owner_id' => $user->id, 'memory' => $data['memory'], 'swap' => $data['swap'], 'disk' => $data['disk'], 'io' => $data['io'], 'cpu' => $data['cpu'], - 'oom_disabled' => (isset($data['oom_disabled'])) ? true : false, + 'oom_disabled' => isset($data['oom_disabled']), 'allocation_id' => $allocation->id, 'service_id' => $data['service_id'], 'option_id' => $data['option_id'], @@ -327,7 +328,7 @@ class ServerRepository 'type' => $service->folder, 'option' => $option->tag, 'pack' => (isset($pack)) ? $pack->uuid : null, - 'skip_scripting' => isset($data['skip_scripting']), + 'skip_scripts' => $server->skip_scripts, ], 'keys' => [ (string) $server->daemonSecret => $this->daemonPermissions, @@ -621,13 +622,93 @@ class ServerRepository } } + /** + * Update the service configuration for a server. + * + * @param int $id + * @param array $data + * @return void + * + * @throws \GuzzleHttp\Exception\RequestException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\DisplayValidationException + */ + protected function changeService($id, array $data) + { + + } + + protected function processVariables(Models\Server $server, $data, $admin = false) + { + $server->load('option.variables'); + + if ($admin) { + $server->startup = $data['startup']; + $server->save(); + } + + if ($server->option->variables) { + foreach ($server->option->variables as &$variable) { + $set = isset($data['env_' . $variable->id]); + + // If user is not an admin and are trying to edit a non-editable field + // or an invisible field just silently skip the variable. + if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) { + continue; + } + + // Perform Field Validation + $validator = Validator::make([ + 'variable_value' => ($set) ? $data['env_' . $variable->id] : null, + ], [ + 'variable_value' => $variable->rules, + ]); + + if ($validator->fails()) { + throw new DisplayValidationException(json_encode( + collect([ + 'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'], + ])->merge($validator->errors()->toArray()) + )); + } + + $svar = Models\ServerVariable::firstOrNew([ + 'server_id' => $server->id, + 'variable_id' => $variable->id, + ]); + + // Set the value; if one was not passed set it to the default value + if ($set) { + $svar->variable_value = $data['env_' . $variable->id]; + + // Not passed, check if this record exists if so keep value, otherwise set default + } else { + $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value; + } + + $svar->save(); + } + } + + // Reload Variables + $server->load('variables'); + return $server->option->variables->map(function ($item, $key) use ($server) { + $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + + return [ + 'variable' => $item->env_variable, + 'value' => (! is_null($display)) ? $display : $item->default_value, + ]; + }); + } + /** * Update the startup details for a server. * * @param int $id * @param array $data * @param bool $admin - * @return void + * @return bool * * @throws \GuzzleHttp\Exception\RequestException * @throws \Pterodactyl\Exceptions\DisplayException @@ -636,78 +717,110 @@ class ServerRepository public function updateStartup($id, array $data, $admin = false) { $server = Models\Server::with('variables', 'option.variables')->findOrFail($id); + $hasServiceChanges = false; - DB::transaction(function () use ($admin, $data, $server) { - if (isset($data['startup']) && $admin) { - $server->startup = $data['startup']; - $server->save(); + if ($admin) { + // User is an admin, lots of things to do here. + $validator = Validator::make($data, [ + 'startup' => 'required|string', + 'skip_scripts' => 'sometimes|required|boolean', + 'service_id' => 'required|numeric|min:1|exists:services,id', + 'option_id' => 'required|numeric|min:1|exists:service_options,id', + 'pack_id' => 'sometimes|nullable|numeric|min:0', + ]); + + if ((int) $data['pack_id'] < 1) { + $data['pack_id'] = null; } - if ($server->option->variables) { - foreach ($server->option->variables as &$variable) { - $set = isset($data['env_' . $variable->id]); - - // If user is not an admin and are trying to edit a non-editable field - // or an invisible field just silently skip the variable. - if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) { - continue; - } - - // Perform Field Validation - $validator = Validator::make([ - 'variable_value' => ($set) ? $data['env_' . $variable->id] : null, - ], [ - 'variable_value' => $variable->rules, - ]); - - if ($validator->fails()) { - throw new DisplayValidationException(json_encode( - collect([ - 'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'], - ])->merge($validator->errors()->toArray()) - )); - } - - $svar = Models\ServerVariable::firstOrNew([ - 'server_id' => $server->id, - 'variable_id' => $variable->id, - ]); - - // Set the value; if one was not passed set it to the default value - if ($set) { - $svar->variable_value = $data['env_' . $variable->id]; - - // Not passed, check if this record exists if so keep value, otherwise set default - } else { - $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value; - } - - $svar->save(); - } + if ($validator->fails()) { + throw new DisplayValidationException(json_encode($validator->errors())); } - // Reload Variables - $server->load('variables'); - $environment = $server->option->variables->map(function ($item, $key) use ($server) { - $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); + if ( + $server->service_id != $data['service_id'] || + $server->option_id != $data['option_id'] || + $server->pack_id != $data['pack_id'] + ) { + $hasServiceChanges = true; + } + } - return [ - 'variable' => $item->env_variable, - 'value' => (! is_null($display)) ? $display : $item->default_value, - ]; + // If user isn't an administrator, this function is being access from the front-end + // Just try to update specific variables. + if (! $admin || ! $hasServiceChanges) { + return DB::transaction(function () use ($admin, $data, $server) { + $environment = $this->processVariables($server, $data, $admin); + + $server->node->guzzleClient([ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $server->node->daemonSecret, + ])->request('PATCH', '/server', [ + 'json' => [ + 'build' => [ + 'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup])->toArray(), + ], + ], + ]); + + return false; }); + } + + // Validate those Service Option Variables + // We know the service and option exists because of the validation. + // We need to verify that the option exists for the service, and then check for + // any required variable fields. (fields are labeled env_) + $option = Models\ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first(); + if (! $option) { + throw new DisplayException('The requested service option does not exist for the specified service.'); + } + + // Validate the Pack + if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) { + $data['pack_id'] = null; + } else { + $pack = Models\Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first(); + if (! $pack) { + throw new DisplayException('The requested service pack does not seem to exist for this combination.'); + } + } + + return DB::transaction(function () use ($admin, $data, $server) { + $server->installed = 0; + $server->service_id = $data['service_id']; + $server->option_id = $data['option_id']; + $server->pack_id = $data['pack_id']; + $server->skip_scripts = isset($data['skip_scripts']); + $server->save(); + + $server->variables->each->delete(); + + $server->load('service', 'pack'); + + // Send New Environment + $environment = $this->processVariables($server, $data, $admin); $server->node->guzzleClient([ 'X-Access-Server' => $server->uuid, 'X-Access-Token' => $server->node->daemonSecret, - ])->request('PATCH', '/server', [ + ])->request('POST', '/server/reinstall', [ 'json' => [ 'build' => [ - 'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup]), + 'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup])->toArray(), + ], + 'service' => [ + 'type' => $server->option->service->folder, + 'option' => $server->option->tag, + 'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null, + 'skip_scripts' => $server->skip_scripts, ], ], ]); + + return true; }); + } /** diff --git a/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php new file mode 100644 index 000000000..07afdfeea --- /dev/null +++ b/database/migrations/2017_04_21_151432_AddServiceScriptTrackingToServers.php @@ -0,0 +1,32 @@ +boolean('skip_scripts')->default(false)->after('description'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('skip_scripts'); + }); + } +} diff --git a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php index 2c1964bec..c83b48cb0 100644 --- a/resources/themes/pterodactyl/admin/servers/view/startup.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/startup.blade.php @@ -64,48 +64,179 @@

Edit your server's startup command here. The following variables are available by default: @{{SERVER_MEMORY}}, @{{SERVER_IP}}, and @{{SERVER_PORT}}.

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

{{ $variable->name }}

-
-
- -

{{ $variable->description }}

-

- @if($variable->required)Required@elseOptional@endif - @if($variable->user_viewable)Visible@elseHidden@endif - @if($variable->user_editable)Editable@elseLocked@endif +

+
+
+
+
+

Service Configuration

+
+
+
+

+ Changing any of the below values will result in the server processing a re-install command. The server will be stopped and will then proceede. + If you are changing the pack, exisiting data may be overwritten. If you would like the service scripts to not run, ensure the box is checked at the bottom. +

+

+ This is a destructive operation in many cases. This server will be stopped immediately in order for this action to proceede.

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

{{ $variable->name }}

+
+
+ +

{{ $variable->description }}

+

+ @if($variable->required)Required@elseOptional@endif + @if($variable->user_viewable)Visible@elseHidden@endif + @if($variable->user_editable)Editable@elseLocked@endif +

+
+ +
+
+ @endforeach +
+
@endsection @section('footer-scripts') @parent + {!! Theme::js('vendor/lodash/lodash.js') !!} + @endsection