Skip to content

Commit a9d0b4a

Browse files
committed
Add support for setting IP aliases though panel
1 parent 723e34a commit a9d0b4a

File tree

6 files changed

+188
-94
lines changed

6 files changed

+188
-94
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
77

88
### Added
99
* Support for creating server without having to assign a node and allocation manually. Simply select the checkbox or pass `auto_deploy=true` to the API to auto-select a node and allocation given a location.
10+
* Support for setting IP Aliases through the panel on the node overview page. Also cleaned up allocation removal.
1011

1112
### Changed
1213
* Prevent clicking server start button until server is completely off, not just stopping.

app/Http/Controllers/Admin/NodesController.php

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -96,32 +96,21 @@ public function postNew(Request $request)
9696
public function getView(Request $request, $id)
9797
{
9898
$node = Models\Node::findOrFail($id);
99-
$allocations = [];
100-
$alloc = Models\Allocation::select('ip', 'port', 'assigned_to')->where('node', $node->id)->orderBy('ip', 'asc')->orderBy('port', 'asc')->get();
101-
if ($alloc) {
102-
foreach($alloc as &$alloc) {
103-
if (!array_key_exists($alloc->ip, $allocations)) {
104-
$allocations[$alloc->ip] = [[
105-
'port' => $alloc->port,
106-
'assigned_to' => $alloc->assigned_to
107-
]];
108-
} else {
109-
array_push($allocations[$alloc->ip], [
110-
'port' => $alloc->port,
111-
'assigned_to' => $alloc->assigned_to
112-
]);
113-
}
114-
}
115-
}
99+
116100
return view('admin.nodes.view', [
117101
'node' => $node,
118102
'servers' => Models\Server::select('servers.*', 'users.email as a_ownerEmail', 'services.name as a_serviceName')
119103
->join('users', 'users.id', '=', 'servers.owner')
120104
->join('services', 'services.id', '=', 'servers.service')
121-
->where('node', $id)->paginate(10),
105+
->where('node', $id)->paginate(10, ['*'], 'servers'),
122106
'stats' => Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node', $node->id)->first(),
123107
'locations' => Models\Location::all(),
124-
'allocations' => json_decode(json_encode($allocations), false),
108+
'allocations' => Models\Allocation::select('allocations.*', 'servers.name as assigned_to_name')
109+
->where('allocations.node', $node->id)
110+
->leftJoin('servers', 'servers.id', '=', 'allocations.assigned_to')
111+
->orderBy('allocations.ip', 'asc')
112+
->orderBy('allocations.port', 'asc')
113+
->paginate(20, ['*'], 'allocations'),
125114
]);
126115
}
127116

@@ -151,24 +140,51 @@ public function postView(Request $request, $id)
151140
])->withInput();
152141
}
153142

154-
public function deleteAllocation(Request $request, $id, $ip, $port = null)
143+
public function deallocateSingle(Request $request, $node, $allocation)
155144
{
156-
$query = Models\Allocation::where('node', $id)->whereNull('assigned_to')->where('ip', $ip);
157-
if (is_null($port) || $port === 'undefined') {
158-
$allocation = $query;
159-
} else {
160-
$allocation = $query->where('port', $port)->first();
161-
}
162-
163-
if (!$allocation) {
145+
$query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('id', $allocation)->delete();
146+
if ((int) $query === 0) {
164147
return response()->json([
165148
'error' => 'Unable to find an allocation matching those details to delete.'
166149
], 400);
167150
}
168-
$allocation->delete();
169151
return response('', 204);
170152
}
171153

154+
public function deallocateBlock(Request $request, $node)
155+
{
156+
$query = Models\Allocation::where('node', $node)->whereNull('assigned_to')->where('ip', $request->input('ip'))->delete();
157+
if ((int) $query === 0) {
158+
Alert::danger('There was an error while attempting to delete allocations on that IP.')->flash();
159+
return redirect()->route('admin.nodes.view', [
160+
'id' => $node,
161+
'tab' => 'tab_allocations'
162+
]);
163+
}
164+
Alert::success('Deleted all unallocated ports for <code>' . $request->input('ip') . '</code>.')->flash();
165+
return redirect()->route('admin.nodes.view', [
166+
'id' => $node,
167+
'tab' => 'tab_allocations'
168+
]);
169+
}
170+
171+
public function setAlias(Request $request, $node)
172+
{
173+
if (!$request->input('allocation')) {
174+
return response('Missing required parameters.', 422);
175+
}
176+
177+
try {
178+
$update = Models\Allocation::findOrFail($request->input('allocation'));
179+
$update->ip_alias = $request->input('alias');
180+
$update->save();
181+
182+
return response('', 204);
183+
} catch (\Exception $ex) {
184+
throw $ex;
185+
}
186+
}
187+
172188
public function getAllocationsJson(Request $request, $id)
173189
{
174190
$allocations = Models\Allocation::select('ip')->where('node', $id)->groupBy('ip')->get();

app/Http/Routes/AdminRoutes.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,17 @@ public function map(Router $router) {
243243
'uses' => 'Admin\NodesController@postView'
244244
]);
245245

246-
$router->delete('/view/{id}/allocation/{ip}/{port?}', [
247-
'uses' => 'Admin\NodesController@deleteAllocation'
246+
$router->delete('/view/{id}/deallocate/single/{allocation}', [
247+
'uses' => 'Admin\NodesController@deallocateSingle'
248+
]);
249+
250+
$router->post('/view/{id}/deallocate/block', [
251+
'uses' => 'Admin\NodesController@deallocateBlock'
252+
]);
253+
254+
$router->post('/view/{id}/alias', [
255+
'as' => 'admin.nodes.alias',
256+
'uses' => 'Admin\NodesController@setAlias'
248257
]);
249258

250259
$router->get('/view/{id}/allocations.json', [

public/themes/default/css/pterodactyl.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ body{font-size:13px}
9090
.btn-xxs{padding:2px 6px;font-size:10px;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:2px}
9191
.form-control{height:36px}
9292
.input-group-addon{font-size:12px;}
93+
.input-sm {
94+
height: 30px;
95+
padding: 4px 8px !important;
96+
font-size: 12px !important;
97+
border-radius: 2px;
98+
}
99+
.input-loader {
100+
display: none;
101+
position:relative;
102+
top: -23px;
103+
float: right;
104+
right: 5px;
105+
color: #cccccc;
106+
height: 0;
107+
}
93108
pre{display:block;padding:12px 12px;margin:0;font-size:12px;color:#c7254e;background-color:#f9f2f4;border:1px solid #c7254e;border-radius:0;white-space:pre}
94109
.badge.label-danger {background: #d9534f !important;}
95110
.close {color:#000;opacity:0.2;font-size:1.6em;}
@@ -102,6 +117,9 @@ form .text-muted {margin: 0 0 -5.5px}
102117
.label{border-radius: .25em;padding: .2em .6em .3em;}
103118
kbd{border-radius: .25em}
104119
.modal-open .modal {padding-left: 0px !important;padding-right: 0px !important;overflow-y: scroll;}
120+
.align-middle {
121+
vertical-align: middle !important;
122+
}
105123

106124
/**
107125
* Pillboxes

resources/views/admin/nodes/view.blade.php

Lines changed: 110 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -307,45 +307,6 @@
307307
<div class="tab-pane" id="tab_allocation">
308308
<div class="panel panel-default">
309309
<div class="panel-heading"></div>
310-
<div class="panel-body">
311-
<table class="table table-striped table-bordered table-hover" style="margin-bottom:0;">
312-
<thead>
313-
<td>IP Address</td>
314-
<td>Ports</td>
315-
<td></td>
316-
</thead>
317-
<tbody>
318-
@foreach($allocations as $ip => $ports)
319-
<tr>
320-
<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>
321-
<td>
322-
@foreach($ports as $id => $allocation)
323-
@if (($id % 2) === 0)
324-
@if($allocation->assigned_to === null)
325-
<span style="cursor:pointer" data-action="delete" data-ip="{{ $ip }}" data-port="{{ $allocation->port }}"><i class="fa fa-fw fa-square-o"></i> {{ $allocation->port }} <br /></span>
326-
@else
327-
<i class="fa fa-fw fa-check-square-o"></i> {{ $allocation->port }} <br />
328-
@endif
329-
@endif
330-
@endforeach
331-
</td>
332-
<td>
333-
@foreach($ports as $id => $allocation)
334-
@if (($id % 2) === 1)
335-
@if($allocation->assigned_to === null)
336-
<span style="cursor:pointer" data-action="delete" data-ip="{{ $ip }}" data-port="{{ $allocation->port }}"><i class="fa fa-fw fa-square-o"></i> {{ $allocation->port }} <br /></span>
337-
@else
338-
<i class="fa fa-fw fa-check-square-o"></i> {{ $allocation->port }} <br />
339-
@endif
340-
@endif
341-
@endforeach
342-
</td>
343-
</tr>
344-
@endforeach
345-
</tbody>
346-
</table>
347-
</div>
348-
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
349310
<div class="panel-body">
350311
<h4 style="margin-top:0;">Allocate Additional Ports</h4>
351312
<form action="{{ route('admin.nodes.post.allocations', $node->id) }}" method="POST">
@@ -391,6 +352,43 @@
391352
</div>
392353
</form>
393354
</div>
355+
<div class="panel-heading" style="border-top: 1px solid #ddd;"></div>
356+
<div class="panel-body">
357+
<div class="row">
358+
<table class="table table-hover" style="margin-bottom:0;">
359+
<thead style="font-weight:bold;">
360+
<td>IP Address <i class="fa fa-fw fa-minus-square" style="font-weight:normal;color:#d9534f;cursor:pointer;" data-toggle="modal" data-target="#allocationModal"></i></td>
361+
<td>IP Alias</td>
362+
<td>Port</td>
363+
<td>Assigned To</td>
364+
<td></td>
365+
</thead>
366+
<tbody>
367+
@foreach($allocations as $allocation)
368+
<tr>
369+
<td class="col-sm-3 align-middle">{{ $allocation->ip }}</td>
370+
<td class="col-sm-3 align-middle">
371+
<input class="form-control input-sm" type="text" value="{{ $allocation->ip_alias }}" data-action="set-alias" data-id="{{ $allocation->id }}" placeholder="none" />
372+
<span class="input-loader"><i class="fa fa-refresh fa-spin fa-fw"></i></span>
373+
</td>
374+
<td class="col-sm-2 align-middle">{{ $allocation->port }}</td>
375+
<td class="col-sm-3 align-middle">@if(!is_null($allocation->assigned_to))<a href="{{ route('admin.servers.view', $allocation->assigned_to) }}">{{ $allocation->assigned_to_name }}</a>@endif</td>
376+
<td class="col-sm-1 align-middle">
377+
@if(is_null($allocation->assigned_to))
378+
<a href="#" data-action="deallocate" data-id="{{ $allocation->id }}"><span class="badge label-danger"><i class="fa fa-trash-o"></i></span></a>
379+
@else
380+
<span class="badge label-default"><i class="fa fa-trash-o"></i></span>
381+
@endif
382+
</td>
383+
</tr>
384+
@endforeach
385+
</tbody>
386+
</table>
387+
<div class="col-md-12 text-center">
388+
{{ $allocations->appends(['tab' => 'tab_allocation'])->links() }}
389+
</div>
390+
</div>
391+
</div>
394392
</div>
395393
</div>
396394
<div class="tab-pane" id="tab_servers">
@@ -458,6 +456,38 @@
458456
<div class="col-xs-11" id="col11_setter"></div>
459457
</div>
460458
</div>
459+
<div class="modal fade" id="allocationModal" tabindex="-1" role="dialog">
460+
<div class="modal-dialog" role="document">
461+
<div class="modal-content">
462+
<div class="modal-header">
463+
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
464+
<h4 class="modal-title">Delete Allocations for IP Block</h4>
465+
</div>
466+
<form action="{{ route('admin.nodes.view', $node->id) }}/deallocate/block" method="POST">
467+
<div class="modal-body">
468+
<div class="row">
469+
<div class="col-md-12">
470+
<select class="form-control" name="ip">
471+
<?php $displayed = []; ?>
472+
@foreach($allocations as $allocation)
473+
@if(!array_key_exists($allocation->ip, $displayed))
474+
<option value="{{ $allocation->ip }}">{{ $allocation->ip }}</option>
475+
<?php $displayed[$allocation->ip] = true; ?>
476+
@endif
477+
@endforeach
478+
</select>
479+
</div>
480+
</div>
481+
</div>
482+
<div class="modal-footer">
483+
{{{ csrf_field() }}}
484+
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
485+
<button type="submit" class="btn btn-danger">Delete Allocations</button>
486+
</div>
487+
</form>
488+
</div>
489+
</div>
490+
</div>
461491
<script>
462492
$(document).ready(function () {
463493
$('#sidebar_links').find("a[href='/admin/nodes']").addClass('active');
@@ -655,14 +685,13 @@
655685
$(this).find('i').css('color', 'inherit').addClass('fa-square-o').removeClass('fa-minus-square');
656686
});
657687
658-
$('span[data-action="delete"]').click(function (event) {
688+
$('a[data-action="deallocate"]').click(function (event) {
659689
event.preventDefault();
660690
var element = $(this);
661-
var deleteIp = $(this).data('ip');
662-
var deletePort = $(this).data('port');
691+
var allocation = $(this).data('id');
663692
swal({
664693
title: '',
665-
text: 'Are you sure you want to delete this port?',
694+
text: 'Are you sure you want to delete this allocation?',
666695
type: 'warning',
667696
showCancelButton: true,
668697
allowOutsideClick: true,
@@ -673,27 +702,12 @@
673702
}, function () {
674703
$.ajax({
675704
method: 'DELETE',
676-
url: '{{ route('admin.nodes.view', $node->id) }}/allocation/' + deleteIp + '/' + deletePort,
705+
url: '{{ route('admin.nodes.view', $node->id) }}/deallocate/single/' + allocation,
677706
headers: {
678707
'X-CSRF-TOKEN': '{{ csrf_token() }}'
679708
}
680709
}).done(function (data) {
681-
if (element.hasClass('is-ipblock')) {
682-
var tMatched = 0;
683-
element.parent().parent().find('*').each(function () {
684-
if ($(this).attr('data-port') && $(this).attr('data-ip')) {
685-
$(this).fadeOut();
686-
tMatched++;
687-
}
688-
});
689-
if (tMatched === element.data('total')) {
690-
element.fadeOut();
691-
$('li[data-action="alloc_dropdown_val"][data-value="' + deleteIp + '"]').remove();
692-
element.parent().parent().slideUp().remove();
693-
}
694-
} else {
695-
element.fadeOut();
696-
}
710+
element.parent().parent().addClass('warning').delay(100).fadeOut();
697711
swal({
698712
type: 'success',
699713
title: 'Port Deleted!',
@@ -709,6 +723,42 @@
709723
});
710724
});
711725
726+
var typingTimer;
727+
$('input[data-action="set-alias"]').keyup(function () {
728+
clearTimeout(typingTimer);
729+
$(this).parent().removeClass('has-error has-success');
730+
typingTimer = setTimeout(sendAlias, 700, $(this));
731+
});
732+
733+
var fadeTimers = [];
734+
function sendAlias(element) {
735+
element.parent().find('.input-loader').show();
736+
clearTimeout(fadeTimers[element.data('id')]);
737+
$.ajax({
738+
method: 'POST',
739+
url: '{{ route('admin.nodes.alias', $node->id) }}',
740+
headers: {
741+
'X-CSRF-TOKEN': '{{ csrf_token() }}'
742+
},
743+
data: {
744+
alias: element.val(),
745+
allocation: element.data('id')
746+
}
747+
}).done(function (data) {
748+
element.parent().addClass('has-success');
749+
}).fail(function (jqXHR) {
750+
console.error(jqXHR);
751+
element.parent().addClass('has-error');
752+
}).always(function () {
753+
element.parent().find('.input-loader').hide();
754+
fadeTimers[element.data('id')] = setTimeout(clearHighlight, 2500, element);
755+
});
756+
}
757+
758+
function clearHighlight(element) {
759+
element.parent().removeClass('has-error has-success');
760+
}
761+
712762
});
713763
</script>
714764
@endsection

0 commit comments

Comments
 (0)