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
+
+