diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php new file mode 100644 index 000000000..415300ebb --- /dev/null +++ b/app/Http/Controllers/Admin/NodesController.php @@ -0,0 +1,75 @@ + Models\Node::select( + 'nodes.*', + 'locations.long as a_locationName', + DB::raw('(SELECT COUNT(*) FROM servers WHERE servers.node = nodes.id) as a_serverCount') + )->join('locations', 'nodes.location', '=', 'locations.id')->paginate(20), + ]); + } + + public function getNew(Request $request) + { + return view('admin.nodes.new', [ + 'locations' => Models\Location::all() + ]); + } + + public function postNew(Request $request) + { + try { + $node = new NodeRepository; + $new = $node->create($request->except([ + '_token' + ])); + Alert::success('Successfully created new node. You should allocate some IP addresses to it now.')->flash(); + return redirect()->route('admin.nodes.view', [ + 'id' => $new + ]); + } catch (\Pterodactyl\Exceptions\DisplayValidationException $e) { + return redirect()->route('admin.nodes.new')->withErrors(json_decode($e->getMessage()))->withInput(); + } catch (\Pterodactyl\Exceptions\DisplayException $e) { + Alert::danger($e->getMessage())->flash(); + } catch (\Exception $e) { + Log::error($e); + Alert::danger('An unhandled exception occured while attempting to add this node. Please try again.')->flash(); + } + return redirect()->route('admin.nodes.new')->withInput(); + } + + public function getView(Request $request, $id) + { + $node = Models\Node::findOrFail($id); + return view('admin.nodes.view', [ + 'node' => $node + ]); + } + +} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index e02ca4ebe..c00da60b9 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -137,6 +137,39 @@ class AdminRoutes { }); + // Node Routes + $router->group([ + 'prefix' => 'admin/nodes', + 'middleware' => [ + 'auth', + 'admin' + ] + ], function () use ($router) { + + // View All Nodes + $router->get('/', [ + 'as' => 'admin.nodes', + 'uses' => 'Admin\NodesController@getIndex' + ]); + + // Add New Node + $router->get('/new', [ + 'as' => 'admin.nodes.new', + 'uses' => 'Admin\NodesController@getNew' + ]); + + $router->post('/new', [ + 'uses' => 'Admin\NodesController@postNew' + ]); + + // View Node + $router->get('/view/{id}', [ + 'as' => 'admin.nodes.view', + 'uses' => 'Admin\NodesController@getView' + ]); + + }); + } } diff --git a/app/Models/Node.php b/app/Models/Node.php index 1f605070f..68f1283d0 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -22,6 +22,13 @@ class Node extends Model */ protected $hidden = ['daemonSecret']; + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + /** * @var array */ diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php new file mode 100644 index 000000000..c4316d910 --- /dev/null +++ b/app/Repositories/NodeRepository.php @@ -0,0 +1,66 @@ + 'required|regex:/^([\w .-]{1,100})$/', + 'location' => 'required|numeric|min:1|exists:locations,id', + 'public' => 'required|numeric|between:0,1', + 'fqdn' => 'required|string|unique:nodes,fqdn', + 'scheme' => 'required|regex:/^(http(s)?)$/', + 'memory' => 'required|numeric|min:1', + 'memory_overallocate' => 'required|numeric|min:-1', + 'disk' => 'required|numeric|min:1', + 'disk_overallocate' => 'required|numeric|min:-1', + 'daemonBase' => 'required|regex:/^([\/][\d\w.\-\/]+)$/', + 'daemonSFTP' => 'required|numeric|between:1,65535', + 'daemonListen' => 'required|numeric|between:1,65535' + ]); + + // 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()); + } + + // Verify the FQDN + if (!filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) { + throw new DisplayException('The FQDN provided does not resolve to a valid IP address.'); + } + + // Should we be nulling the overallocations? + $data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate']; + $data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate']; + + // Set the Secret + $uuid = new UuidService; + $data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret'); + + // Store the Data + $node = new Models\Node; + $node->fill($data); + $node->save(); + + return $node->id; + + } + +} diff --git a/database/migrations/2016_01_04_232911_remove_node_ip.php b/database/migrations/2016_01_04_232911_remove_node_ip.php new file mode 100644 index 000000000..b8f9072a8 --- /dev/null +++ b/database/migrations/2016_01_04_232911_remove_node_ip.php @@ -0,0 +1,31 @@ +dropColumn('ip'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('nodes', function (Blueprint $table) { + $table->string('ip')->after('fqdn'); + }); + } +} diff --git a/resources/views/admin/nodes/index.blade.php b/resources/views/admin/nodes/index.blade.php new file mode 100644 index 000000000..7c8c0f818 --- /dev/null +++ b/resources/views/admin/nodes/index.blade.php @@ -0,0 +1,51 @@ +@extends('layouts.admin') + +@section('title') + Node List +@endsection + +@section('content') +
+ +

All Nodes


+ + + + + + + + + + + + + + + @foreach ($nodes as $node) + + + + + + + + + + + @endforeach + +
NameLocationFQDNMemoryDiskServersHTTPSPublic
{{ $node->name }}{{ $node->a_locationName }}{{ $node->fqdn }}{{ $node->memory }} MB{{ $node->disk }} MB{{ $node->a_serverCount }}
+
+
{!! $nodes->render() !!}
+
+
+ +@endsection diff --git a/resources/views/admin/nodes/new.blade.php b/resources/views/admin/nodes/new.blade.php new file mode 100644 index 000000000..d63ee4075 --- /dev/null +++ b/resources/views/admin/nodes/new.blade.php @@ -0,0 +1,168 @@ +@extends('layouts.admin') + +@section('title') + Create Node +@endsection + +@section('content') +
+ +

Create New Node


+
+
+
+
+ +
+ +

Character limits: a-zA-Z0-9_.- and [Space] (min 1, max 100 characters).

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

This must be a fully qualified domain name, you may not enter an IP address or a domain that does not exist. + Why? +

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

You should always leave SSL enabled for nodes. Disabling SSL could allow a malicious user to intercept traffic between the panel and the daemon potentially exposing sensitive information.

+
+
+
+
+
+
+
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+
+
+

Enter the total amount of memory avaliable for new servers. If you would like to allow overallocation of memory enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

+
+
+
+
+
+
+
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+
+
+

Enter the total amount of disk space avaliable for new servers. If you would like to allow overallocation of disk space enter the percentage that you want to allow. To disable checking for overallocation enter -1 into the field. Entering 0 will prevent creating new servers if it would put the node over the limit.

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

The location at which your server files will be stored. Most users do not need to change this.

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

The daemon runs its own SFTP management container and does not use the SSHd process on the main physical server. Do not use the same port that you have assigned for your physcial server's SSH process.

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