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.
This commit is contained in:
parent
aaf9768a92
commit
179481c547
8 changed files with 282 additions and 8 deletions
|
@ -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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
|
@ -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'];
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
10
public/js/admin.min.js
vendored
10
public/js/admin.min.js
vendored
|
@ -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) {
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
|
||||
@section('scripts')
|
||||
@parent
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fuelux/3.13.0/css/fuelux.min.css" />
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highcharts/4.2.1/highcharts.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.7/socket.io.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/fuelux/3.13.0/js/fuelux.min.js"></script>
|
||||
<script src="{{ asset('js/bootstrap-notify.min.js') }}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
@ -285,7 +287,7 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<table class="table table-striped table-bordered table-hover" style="margin-bottom:0;">
|
||||
<thead>
|
||||
<td>IP Address</td>
|
||||
<td>Ports</td>
|
||||
|
@ -294,7 +296,7 @@
|
|||
<tbody>
|
||||
@foreach($allocations as $ip => $ports)
|
||||
<tr>
|
||||
<td>{{ $ip }}</td>
|
||||
<td><span style="cursor:pointer" data-action="delete" data-ip="{{ $ip }}" data-total="{{ count($ports) }}" class="is-ipblock"><i class="fa fa-fw fa-square-o"></i></span> {{ $ip }}</td>
|
||||
<td>
|
||||
@foreach($ports as $id => $allocation)
|
||||
@if (($id % 2) === 0)
|
||||
|
@ -322,6 +324,51 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
|
||||
<div class="panel-body">
|
||||
<h4 style="margin-top:0;">Allocate Additional Ports</h4>
|
||||
<form action="{{ route('admin.nodes.post.allocations', $node->id) }}" method="POST">
|
||||
<div class="row" id="duplicate">
|
||||
<div class="col-md-4 fuelux">
|
||||
<label for="" class="control-label">IP Address</label>
|
||||
<div class="input-group input-append dropdown combobox allocationComboBox" data-initialize="combobox">
|
||||
<input type="text" name="allocate_ip[]" class="form-control pillbox_ip" style="border-right:0;">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
@foreach($allocations as $ip => $ports)
|
||||
<li data-action="alloc_dropdown_val" data-value="{{ $ip }}"><a href="#">{{ $ip }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-7 col-xs-10 fuelux">
|
||||
<label for="" class="control-label">Ports</label>
|
||||
<div class="pillbox allocationPillbox" data-initialize="pillbox">
|
||||
<ul class="clearfix pill-group">
|
||||
<li class="pillbox-input-wrap btn-group">
|
||||
<input type="text" class="form-control dropdown-toggle pillbox-add-item" placeholder="add port">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input name="allocate_port[]" type="hidden" class="pillboxMain"/>
|
||||
</div>
|
||||
<div class="form-group col-md-1 col-xs-2" style="margin-left: -10px;">
|
||||
<label for="" class="control-label"> </label>
|
||||
<button class="btn btn-danger btn-allocate-delete removeClone disabled"><i class="fa fa-close"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<hr />
|
||||
{!! csrf_field() !!}
|
||||
<input type="submit" class="btn btn-sm btn-primary" value="Add Ports" />
|
||||
<button class="btn btn-success btn-sm cloneElement">Add More Rows</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tab_servers">
|
||||
|
@ -384,6 +431,36 @@ $(document).ready(function () {
|
|||
placement: 'auto'
|
||||
});
|
||||
|
||||
$('.cloneElement').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
var cloned = $('#duplicate').clone();
|
||||
var rnd = randomKey(10);
|
||||
cloned.find('.allocationPillbox').removeClass('allocationPillbox').addClass('allocationPillbox_' + rnd);
|
||||
cloned.find('.pillboxMain').removeClass('pillboxMain').addClass('pillbox_' + rnd);
|
||||
cloned.find('.removeClone').removeClass('disabled');
|
||||
cloned.find('.pillbox_ip').removeClass('pillbox_ip').addClass('pillbox_ip_' + rnd);
|
||||
cloned.insertAfter('#duplicate');
|
||||
$('.allocationPillbox_' + rnd).pillbox();
|
||||
$('.allocationPillbox_' + rnd).on('added.fu.pillbox edited.fu.pillbox removed.fu.pillbox', function pillboxChanged() {
|
||||
$('.pillbox_' + rnd).val(JSON.stringify($('.allocationPillbox_' + rnd).pillbox('items')));
|
||||
});
|
||||
$('.removeClone').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
var element = $(this);
|
||||
element.parent().parent().slideUp(function () {
|
||||
element.remove();
|
||||
$('.pillbox_' + rnd).remove();
|
||||
$('.pillbox_ip_' + rnd).remove();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
$('.allocationPillbox').pillbox();
|
||||
$('.allocationComboBox').combobox();
|
||||
$('.allocationPillbox').on('added.fu.pillbox edited.fu.pillbox removed.fu.pillbox', function pillboxChanged() {
|
||||
$('.pillboxMain').val(JSON.stringify($('.allocationPillbox').pillbox('items')));
|
||||
});
|
||||
|
||||
var notifySocketError = false;
|
||||
var Status = {
|
||||
0: 'Off',
|
||||
|
@ -664,6 +741,20 @@ $(document).ready(function () {
|
|||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
}).done(function (data) {
|
||||
if (element.hasClass('is-ipblock')) {
|
||||
var tMatched = 0;
|
||||
element.parent().parent().find('*').each(function () {
|
||||
if ($(this).attr('data-port') && $(this).attr('data-ip')) {
|
||||
$(this).fadeOut();
|
||||
tMatched++;
|
||||
}
|
||||
});
|
||||
if (tMatched === element.data('total')) {
|
||||
element.fadeOut();
|
||||
$('li[data-action="alloc_dropdown_val"][data-value="' + deleteIp + '"]').remove();
|
||||
element.parent().parent().slideUp().remove();
|
||||
}
|
||||
}
|
||||
swal({
|
||||
type: 'success',
|
||||
title: 'Port Deleted!',
|
||||
|
|
Loading…
Reference in a new issue