From 4953608aee12ba15d36f16fe7ccae8ab4e0c94ba Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 2 Jan 2016 23:21:22 -0500 Subject: [PATCH] Add build configuration to server management. Allows modification of certain settings, as well as assigning additional IP addresses and ports. --- .../Controllers/Admin/ServersController.php | 89 ++++++++-- app/Http/Routes/AdminRoutes.php | 2 + app/Repositories/ServerRepository.php | 162 +++++++++++++++++- resources/views/admin/servers/view.blade.php | 116 ++++++++++++- 4 files changed, 352 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 3831e9a0f..3ac9dec09 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -50,20 +50,24 @@ class ServersController extends Controller public function getView(Request $request, $id) { + $server = Models\Server::select( + 'servers.*', + 'nodes.name as a_nodeName', + 'users.email as a_ownerEmail', + 'locations.long as a_locationName', + 'services.name as a_serviceName', + 'service_options.name as a_servceOptionName' + )->join('nodes', 'servers.node', '=', 'nodes.id') + ->join('users', 'servers.owner', '=', 'users.id') + ->join('locations', 'nodes.location', '=', 'locations.id') + ->join('services', 'servers.service', '=', 'services.id') + ->join('service_options', 'servers.option', '=', 'service_options.id') + ->first(); + return view('admin.servers.view', [ - 'server' => Models\Server::select( - 'servers.*', - 'nodes.name as a_nodeName', - 'users.email as a_ownerEmail', - 'locations.long as a_locationName', - 'services.name as a_serviceName', - 'service_options.name as a_servceOptionName' - )->join('nodes', 'servers.node', '=', 'nodes.id') - ->join('users', 'servers.owner', '=', 'users.id') - ->join('locations', 'nodes.location', '=', 'locations.id') - ->join('services', 'servers.service', '=', 'services.id') - ->join('service_options', 'servers.option', '=', 'service_options.id') - ->first() + 'server' => $server, + 'assigned' => Models\Allocation::select('id', 'ip', 'port')->where('assigned_to', $id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(), + 'unassigned' => Models\Allocation::select('id', 'ip', 'port')->where('node', $server->node)->whereNull('assigned_to')->orderBy('ip', 'asc')->orderBy('port', 'asc')->get() ]); } @@ -225,4 +229,63 @@ class ServersController extends Controller } } + public function postUpdateServerToggleBuild(Request $request, $id) { + $server = Models\Server::findOrFail($id); + $node = Models\Node::findOrFail($server->node); + $client = Models\Node::guzzleRequest($server->node); + + try { + $res = $client->request('POST', '/server/rebuild', [ + 'headers' => [ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $node->daemonSecret + ] + ]); + Alert::success('A rebuild has been queued successfully. It will run the next time this server is booted.')->flash(); + } catch (\GuzzleHttp\Exception\TransferException $ex) { + Log::warning($ex); + Alert::danger('An error occured while attempting to toggle a rebuild: ' . $ex->getMessage())->flash(); + } + + return redirect()->route('admin.servers.view', [ + 'id' => $id, + 'tab' => 'tab_manage' + ]); + } + + public function postUpdateServerUpdateBuild(Request $request, $id) + { + try { + + $server = new ServerRepository; + $server->changeBuild($id, [ + 'default' => $request->input('default'), + 'add_additional' => $request->input('add_additional'), + 'remove_additional' => $request->input('remove_additional'), + 'memory' => $request->input('memory'), + 'swap' => $request->input('swap'), + 'io' => $request->input('io'), + 'cpu' => $request->input('cpu'), + ]); + Alert::success('Server details were successfully updated.')->flash(); + } catch (\Exception $e) { + + if ($e instanceof \Pterodactyl\Exceptions\DisplayValidationException) { + return redirect()->route('admin.servers.view', [ + 'id' => $id, + 'tab' => 'tab_build' + ])->withErrors(json_decode($e->getMessage()))->withInput(); + } else if ($e instanceof \Pterodactyl\Exceptions\DisplayException) { + Alert::danger($e->getMessage())->flash(); + } else { + Log::error($e); + Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash(); + } + } + return redirect()->route('admin.servers.view', [ + 'id' => $id, + 'tab' => 'tab_build' + ]); + } + } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 709bf3993..9ec7858f0 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -31,6 +31,8 @@ class AdminRoutes { $router->get('/view/{id}', [ 'as' => 'admin.servers.view', 'uses' => 'Admin\ServersController@getView' ]); $router->post('/view/{id}/details', [ 'uses' => 'Admin\ServersController@postUpdateServerDetails' ]); + $router->post('/view/{id}/rebuild', [ 'uses' => 'Admin\ServersController@postUpdateServerToggleBuild' ]); + $router->post('/view/{id}/build', [ 'uses' => 'Admin\ServersController@postUpdateServerUpdateBuild' ]); $router->post('/new', [ 'uses' => 'Admin\ServersController@postNewServer']); $router->post('/new/get-nodes', [ 'uses' => 'Admin\ServersController@postNewServerGetNodes' ]); diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 170bc97c6..958895a5e 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -226,7 +226,7 @@ class ServerRepository // Validate Fields $validator = Validator::make($data, [ 'owner' => 'email|exists:users,email', - 'name' => 'regex:([\w -]{4,35})' + 'name' => 'regex:^([\w -]{4,35})$' ]); // Run validator, throw catchable and displayable exception if it fails. @@ -304,11 +304,169 @@ class ServerRepository throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode()); } } catch (\Exception $ex) { - DB::rollback(); + DB::rollBack(); Log::error($ex); throw new DisplayException('An error occured while attempting to update this server\'s information.'); } } + /** + * [changeBuild description] + * @param integer $id + * @param array $data + * @return boolean + */ + public function changeBuild($id, array $data) + { + + $validator = Validator::make($data, [ + 'default' => [ + 'string', + 'regex:/^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5])):(\d{1,5})$/' + ], + 'add_additional' => 'array', + 'remove_additional' => 'array', + 'memory' => 'integer|min:0', + 'swap' => 'integer|min:0', + 'io' => 'integer|min:10|max:1000', + 'cpu' => 'integer|min:0', + 'disk' => 'integer|min:0' + ]); + + // Run validator, throw catchable and displayable exception if it fails. + // Exception includes a JSON result of failed validation rules. + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + DB::beginTransaction(); + $server = Models\Server::findOrFail($id); + + if (isset($data['default'])) { + list($ip, $port) = explode(':', $data['default']); + if ($ip === $server->ip && $port === $server->port) { + continue; + } + + $allocation = Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->get(); + if (!$allocation) { + throw new DisplayException('The assigned default connection (' . $ip . ':' . $prot . ') is not allocated to this server.'); + } + + $server->ip = $ip; + $server->port = $port; + } + + // Remove Assignments + if (isset($data['remove_additional'])) { + foreach ($data['remove_additional'] as $id => $combo) { + list($ip, $port) = explode(':', $combo); + // Invalid, not worth killing the whole thing, we'll just skip over it. + if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) { + continue; + } + + // Can't remove the assigned IP/Port combo + if ($ip === $server->ip && $port === $server->port) { + continue; + } + + Models\Allocation::where('ip', $ip)->where('port', $port)->where('assigned_to', $server->id)->update([ + 'assigned_to' => null + ]); + } + } + + // Add Assignments + if (isset($data['add_additional'])) { + foreach ($data['add_additional'] as $id => $combo) { + list($ip, $port) = explode(':', $combo); + // Invalid, not worth killing the whole thing, we'll just skip over it. + if (!filter_var($ip, FILTER_VALIDATE_IP) || !preg_match('/^(\d{1,5})$/', $port)) { + continue; + } + + // Don't allow double port assignments + if (Models\Allocation::where('port', $port)->where('assigned_to', $server->id)->count() !== 0) { + continue; + } + + Models\Allocation::where('ip', $ip)->where('port', $port)->whereNull('assigned_to')->update([ + 'assigned_to' => $server->id + ]); + } + } + + // Loop All Assignments + $additionalAssignments = []; + $assignments = Models\Allocation::where('assigned_to', $server->id)->get(); + foreach ($assignments as &$assignment) { + if (array_key_exists((string) $assignment->ip, $additionalAssignments)) { + array_push($additionalAssignments[ (string) $assignment->ip ], (int) $assignment->port); + } else { + $additionalAssignments[ (string) $assignment->ip ] = [ (int) $assignment->port ]; + } + } + + // @TODO: verify that server can be set to this much memory without + // going over node limits. + if (isset($data['memory'])) { + $server->memory = $data['memory']; + } + + if (isset($data['swap'])) { + $server->swap = $data['swap']; + } + + // @TODO: verify that server can be set to this much disk without + // going over node limits. + if (isset($data['disk'])) { + $server->disk = $data['disk']; + } + + if (isset($data['cpu'])) { + $server->cpu = $data['cpu']; + } + + if (isset($data['io'])) { + $server->io = $data['io']; + } + + try { + + $node = Models\Node::getByID($server->node); + $client = Models\Node::guzzleRequest($server->node); + + $client->request('PATCH', '/server', [ + 'headers' => [ + 'X-Access-Server' => $server->uuid, + 'X-Access-Token' => $node->daemonSecret + ], + 'json' => [ + 'build' => [ + 'default' => [ + 'ip' => $server->ip, + 'port' => (int) $server->port + ], + 'ports|overwrite' => $additionalAssignments, + 'memory' => (int) $server->memory, + 'swap' => (int) $server->swap, + 'io' => (int) $server->io, + 'cpu' => (int) $server->cpu, + 'disk' => (int) $server->disk + ] + ] + ]); + + $server->save(); + DB::commit(); + return true; + } catch (\GuzzleHttp\Exception\TransferException $ex) { + DB::rollBack(); + throw new DisplayException('An error occured while attempting to update the configuration: ' . $ex->getMessage()); + } + + } + } diff --git a/resources/views/admin/servers/view.blade.php b/resources/views/admin/servers/view.blade.php index 982526d91..5d66de5d1 100644 --- a/resources/views/admin/servers/view.blade.php +++ b/resources/views/admin/servers/view.blade.php @@ -33,6 +33,7 @@
  • About
  • Details
  • Build Configuration
  • +
  • Startup Settings
  • Manage
  • @@ -139,7 +140,103 @@
    - Build +
    +
    +
    + +
    + + 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) +
    + + ip == $server->ip && $assignment->port == $server->port) checked="checked" @endif name="default" value="{{ $assignment->ip }}:{{ $assignment->port }}"/> + + +
    + @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() !!} + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Startup
    @@ -147,7 +244,18 @@
    - Manage +
    +
    +
    + {!! 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. Please be aware: if you manually updated the server's configuration file, you will need to restart the daemon before doing this, or it will be overwritten.

    +
    A rebuild will automatically occur whenever you edit build configuration settings for the server.
    +
    +
    @@ -156,6 +264,10 @@ @endsection