From 179481c54748ef511c0a0ec28e64cff67d7f981b Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 10 Jan 2016 00:38:16 -0500 Subject: [PATCH] Add support for allocation management on nodes. Allows deleting entire IP blocks, as well as allocating new IPs and Ports via CIDR ranges, single IP, and single ports or a port range. --- .../Controllers/Admin/NodesController.php | 58 ++++++++++- app/Http/Routes/AdminRoutes.php | 14 ++- app/Models/Allocation.php | 7 ++ app/Repositories/NodeRepository.php | 60 ++++++++++++ composer.json | 3 +- public/css/pterodactyl.css | 43 +++++++++ public/js/admin.min.js | 10 ++ resources/views/admin/nodes/view.blade.php | 95 ++++++++++++++++++- 8 files changed, 282 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 8b97e7f92..e0a7f0f46 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -68,7 +68,7 @@ class NodesController extends Controller { $node = Models\Node::findOrFail($id); $allocations = []; - $alloc = Models\Allocation::select('ip', 'port', 'assigned_to')->where('node', $node->id)->get(); + $alloc = Models\Allocation::select('ip', 'port', 'assigned_to')->where('node', $node->id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get(); if ($alloc) { foreach($alloc as &$alloc) { if (!array_key_exists($alloc->ip, $allocations)) { @@ -122,9 +122,15 @@ class NodesController extends Controller ])->withInput(); } - public function deletePortAllocation(Request $request, $id, $ip, $port) + public function deleteAllocation(Request $request, $id, $ip, $port = null) { - $allocation = Models\Allocation::where('node', $id)->whereNull('assigned_to')->where('ip', $ip)->where('port', $port)->first(); + $query = Models\Allocation::where('node', $id)->whereNull('assigned_to')->where('ip', $ip); + if (is_null($port) || $port === 'undefined') { + $allocation = $query; + } else { + $allocation = $query->where('port', $port)->first(); + } + if (!$allocation) { return response()->json([ 'error' => 'Unable to find an allocation matching those details to delete.' @@ -134,4 +140,50 @@ class NodesController extends Controller return response('', 204); } + public function getAllocationsJson(Request $request, $id) + { + $allocations = Models\Allocation::select('ip')->where('node', $id)->groupBy('ip')->get(); + return response()->json($allocations); + } + + public function postAllocations(Request $request, $id) + { + $processedData = []; + foreach($request->input('allocate_ip') as $ip) { + if (!array_key_exists($ip, $processedData)) { + $processedData[$ip] = []; + } + } + + foreach($request->input('allocate_port') as $portid => $ports) { + if (array_key_exists($portid, $request->input('allocate_ip'))) { + $json = json_decode($ports); + if (json_last_error() === 0 && !empty($json)) { + foreach($json as &$parsed) { + array_push($processedData[$request->input('allocate_ip')[$portid]], $parsed->value); + } + } + } + } + + try { + if(empty($processedData)) { + throw new \Pterodactyl\Exceptions\DisplayException('It seems that no data was passed to this function.'); + } + $node = new NodeRepository; + $node->addAllocations($id, $processedData); + Alert::success('Successfully added new allocations to this node.')->flash(); + } 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 allocations this node. Please try again.')->flash(); + } finally { + return redirect()->route('admin.nodes.view', [ + 'id' => $id, + 'tab' => 'tab_allocation' + ]); + } + } + } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index ffc21220c..f84fdd826 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -172,8 +172,18 @@ class AdminRoutes { 'uses' => 'Admin\NodesController@postView' ]); - $router->delete('/view/{id}/allocation/{ip}/{port}', [ - 'uses' => 'Admin\NodesController@deletePortAllocation' + $router->delete('/view/{id}/allocation/{ip}/{port?}', [ + 'uses' => 'Admin\NodesController@deleteAllocation' + ]); + + $router->get('/view/{id}/allocations.json', [ + 'as' => 'admin.nodes.view.allocations', + 'uses' => 'Admin\NodesController@getAllocationsJson' + ]); + + $router->post('/view/{id}/allocations', [ + 'as' => 'admin.nodes.post.allocations', + 'uses' => 'Admin\NodesController@postAllocations' ]); }); diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 651044de9..e8126f9f9 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -14,4 +14,11 @@ class Allocation extends Model */ protected $table = 'allocations'; + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + } diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/NodeRepository.php index 91d9eaa6e..5611cc260 100644 --- a/app/Repositories/NodeRepository.php +++ b/app/Repositories/NodeRepository.php @@ -2,11 +2,13 @@ namespace Pterodactyl\Repositories; +use DB; use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; +use IPTools\Network; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayValidationException; @@ -123,4 +125,62 @@ class NodeRepository { } + public function addAllocations($id, array $allocations) + { + $node = Models\Node::findOrFail($id); + + DB::beginTransaction(); + foreach($allocations as $rawIP => $ports) { + $parsedIP = Network::parse($rawIP); + foreach($parsedIP as $ip) { + foreach($ports as $port) { + if (!is_int($port) && !preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) { + throw new DisplayException('The mapping for ' . $port . ' is invalid and cannot be processed.'); + } + if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) { + foreach(range($matches[1], $matches[2]) as $assignPort) { + $alloc = Models\Allocation::firstOrNew([ + 'node' => $node->id, + 'ip' => $ip, + 'port' => $assignPort + ]); + if (!$alloc->exists) { + $alloc->fill([ + 'node' => $node->id, + 'ip' => $ip, + 'port' => $assignPort, + 'assigned_to' => null + ]); + $alloc->save(); + } + } + } else { + $alloc = Models\Allocation::firstOrNew([ + 'node' => $node->id, + 'ip' => $ip, + 'port' => $port + ]); + if (!$alloc->exists) { + $alloc->fill([ + 'node' => $node->id, + 'ip' => $ip, + 'port' => $port, + 'assigned_to' => null + ]); + $alloc->save(); + } + } + } + } + } + + try { + DB::commit(); + return true; + } catch (\Exception $ex) { + DB::rollBack(); + throw $ex; + } + } + } diff --git a/composer.json b/composer.json index bf2523bfa..052e8cc2c 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "kris/laravel-form-builder": "^1.6", "pragmarx/google2fa": "^0.7.1", "webpatser/laravel-uuid": "^2.0", - "prologue/alerts": "^0.4.0" + "prologue/alerts": "^0.4.0", + "s1lentium/iptools": "^1.0" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/public/css/pterodactyl.css b/public/css/pterodactyl.css index e7347701a..89767aeb9 100755 --- a/public/css/pterodactyl.css +++ b/public/css/pterodactyl.css @@ -80,3 +80,46 @@ form .text-muted {margin: 0 0 -5.5px} .label{border-radius: .25em;padding: .2em .6em .3em;} kbd{border-radius: .25em} .modal-open .modal {padding-left: 0px !important;padding-right: 0px !important;overflow-y: scroll;} + +/** + * Pillboxes + */ +.fuelux .pillbox { + border-radius: 0 !important; +} + +li.btn.btn-default.pill { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; + color:#fff; + background-color:#008cba; + border-color:#0079a1; +} + +li.btn.btn-default.pill:active,li.btn.btn-default.pill:focus,li.btn.btn-default.pill:hover { + background-color:#006687; + border-color:#004b63 +} + + +.fuelux .pillbox>.pill-group .form-control { + height: 26px !important; +} + +.fuelux .pillbox .pillbox-input-wrap { + margin: 0 !important; +} + +.btn-allocate-delete { + height:34px; + width:34px; + padding:0; +} + +@media (max-width:992px){ + .btn-allocate-delete { + margin-top:22px; + } +} diff --git a/public/js/admin.min.js b/public/js/admin.min.js index 13105ba3d..07217aa42 100755 --- a/public/js/admin.min.js +++ b/public/js/admin.min.js @@ -1,3 +1,13 @@ +function randomKey(length) { + var text = ''; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + for( var i=0; i < length; i++ ) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + + return text; +} $(document).ready(function () { $.urlParam=function(name){var results=new RegExp("[\\?&]"+name+"=([^&#]*)").exec(decodeURIComponent(window.location.href));if(results==null){return null}else{return results[1]||0}};function getPageName(url){var index=url.lastIndexOf("/")+1;var filenameWithExtension=url.substr(index);var filename=filenameWithExtension.split(".")[0];return filename} function centerModal(element) { diff --git a/resources/views/admin/nodes/view.blade.php b/resources/views/admin/nodes/view.blade.php index 8880ee6dc..c5869e8cd 100644 --- a/resources/views/admin/nodes/view.blade.php +++ b/resources/views/admin/nodes/view.blade.php @@ -6,8 +6,10 @@ @section('scripts') @parent + +