Skip to content

Commit 9b93629

Browse files
committed
Add UI for client API keys
1 parent 2017e64 commit 9b93629

File tree

9 files changed

+213
-83
lines changed

9 files changed

+213
-83
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
88
* Fixes a bug when reinstalling a server that would not mark the server as installing, resulting in some UI issues.
99
* Handle 404 errors from missing models in the application API bindings correctly.
1010

11+
### Added
12+
* Adds back client API for sending commands or power toggles to a server though the Panel API: `/api/client/servers/<identifier>`
13+
1114
## v0.7.3 (Derelict Dermodactylus)
1215
### Fixed
1316
* Fixes server creation API endpoint not passing the provided `external_id` to the creation service.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Base;
4+
5+
use Illuminate\View\View;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Http\Response;
8+
use Pterodactyl\Models\ApiKey;
9+
use Illuminate\Http\RedirectResponse;
10+
use Prologue\Alerts\AlertsMessageBag;
11+
use Pterodactyl\Http\Controllers\Controller;
12+
use Pterodactyl\Services\Api\KeyCreationService;
13+
use Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest;
14+
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
15+
16+
class ClientApiController extends Controller
17+
{
18+
/**
19+
* @var \Prologue\Alerts\AlertsMessageBag
20+
*/
21+
private $alert;
22+
23+
/**
24+
* @var \Pterodactyl\Services\Api\KeyCreationService
25+
*/
26+
private $creationService;
27+
28+
/**
29+
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface
30+
*/
31+
private $repository;
32+
33+
/**
34+
* ClientApiController constructor.
35+
*
36+
* @param \Prologue\Alerts\AlertsMessageBag $alert
37+
* @param \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface $repository
38+
* @param \Pterodactyl\Services\Api\KeyCreationService $creationService
39+
*/
40+
public function __construct(AlertsMessageBag $alert, ApiKeyRepositoryInterface $repository, KeyCreationService $creationService)
41+
{
42+
$this->alert = $alert;
43+
$this->creationService = $creationService;
44+
$this->repository = $repository;
45+
}
46+
47+
/**
48+
* Return all of the API keys available to this user.
49+
*
50+
* @param \Illuminate\Http\Request $request
51+
* @return \Illuminate\View\View
52+
*/
53+
public function index(Request $request): View
54+
{
55+
return view('base.api.index', [
56+
'keys' => $this->repository->getAccountKeys($request->user()),
57+
]);
58+
}
59+
60+
/**
61+
* Render UI to allow creation of an API key.
62+
*
63+
* @return \Illuminate\View\View
64+
*/
65+
public function create(): View
66+
{
67+
return view('base.api.new');
68+
}
69+
70+
/**
71+
* Create the API key and return the user to the key listing page.
72+
*
73+
* @param \Pterodactyl\Http\Requests\Base\CreateClientApiKeyRequest $request
74+
* @return \Illuminate\Http\RedirectResponse
75+
*
76+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
77+
*/
78+
public function store(CreateClientApiKeyRequest $request): RedirectResponse
79+
{
80+
$allowedIps = null;
81+
if (! is_null($request->input('allowed_ips'))) {
82+
$allowedIps = json_encode(explode(PHP_EOL, $request->input('allowed_ips')));
83+
}
84+
85+
$this->creationService->setKeyType(ApiKey::TYPE_ACCOUNT)->handle([
86+
'memo' => $request->input('memo'),
87+
'allowed_ips' => $allowedIps,
88+
'user_id' => $request->user()->id,
89+
]);
90+
91+
$this->alert->success('A new client API key has been generated for your account.')->flash();
92+
93+
return redirect()->route('account.api');
94+
}
95+
96+
/**
97+
* Delete a client's API key from the panel.
98+
*
99+
* @param \Illuminate\Http\Request $request
100+
* @param $identifier
101+
* @return \Illuminate\Http\Response
102+
*/
103+
public function delete(Request $request, $identifier): Response
104+
{
105+
$this->repository->deleteAccountKey($request->user(), $identifier);
106+
107+
return response('', 204);
108+
}
109+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Base;
4+
5+
use Pterodactyl\Http\Requests\FrontendUserFormRequest;
6+
7+
class CreateClientApiKeyRequest extends FrontendUserFormRequest
8+
{
9+
/**
10+
* Validate the data being provided.
11+
*
12+
* @return array
13+
*/
14+
public function rules()
15+
{
16+
return [
17+
'memo' => 'required|string|max:255',
18+
'allowed_ips' => 'nullable|string',
19+
];
20+
}
21+
}

public/js/laroute.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/themes/pterodactyl/admin/api/new.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
@section('content')
1717
<div class="row">
18-
<form method="POST" action="{{ route('admin.api.new') }}">
18+
<form method="POST" action="{{ route('account.api.new') }}">
1919
<div class="col-sm-8 col-xs-12">
2020
<div class="box box-primary">
2121
<div class="box-header with-border">

resources/themes/pterodactyl/base/api/index.blade.php

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,57 +18,70 @@
1818
@endsection
1919

2020
@section('content')
21-
<div class="row">
22-
<div class="col-xs-12">
23-
<div class="box">
24-
<div class="box-header">
25-
<h3 class="box-title">@lang('base.api.index.list')</h3>
26-
<div class="box-tools">
27-
<a href="{{ route('account.api.new') }}"><button class="btn btn-primary btn-sm">Create New</button></a>
21+
<div class="row">
22+
<div class="col-xs-12">
23+
<div class="box box-primary">
24+
<div class="box-header with-border">
25+
<h3 class="box-title">Credentials List</h3>
26+
<div class="box-tools">
27+
<a href="{{ route('account.api.new') }}" class="btn btn-sm btn-primary">Create New</a>
28+
</div>
2829
</div>
29-
</div>
30-
<div class="box-body table-responsive no-padding">
31-
<table class="table table-hover">
32-
<tbody>
30+
<div class="box-body table-responsive no-padding">
31+
<table class="table table-hover">
3332
<tr>
34-
<th>@lang('strings.memo')</th>
35-
<th>@lang('strings.public_key')</th>
36-
<th class="text-right hidden-sm hidden-xs">@lang('strings.last_used')</th>
37-
<th class="text-right hidden-sm hidden-xs">@lang('strings.created')</th>
33+
<th>Key</th>
34+
<th>Memo</th>
35+
<th>Last Used</th>
36+
<th>Created</th>
3837
<th></th>
3938
</tr>
40-
@foreach ($keys as $key)
39+
@foreach($keys as $key)
4140
<tr>
41+
<td>
42+
<code class="toggle-display" style="cursor:pointer" data-toggle="tooltip" data-placement="right" title="Click to Reveal">
43+
<i class="fa fa-key"></i> &bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;
44+
</code>
45+
<code class="hidden" data-attr="api-key">
46+
{{ $key->identifier }}{{ decrypt($key->token) }}
47+
</code>
48+
</td>
4249
<td>{{ $key->memo }}</td>
43-
<td><code>{{ $key->identifier . decrypt($key->token) }}</code></td>
44-
<td class="text-right hidden-sm hidden-xs">
50+
<td>
4551
@if(!is_null($key->last_used_at))
4652
@datetimeHuman($key->last_used_at)
47-
@else
53+
@else
4854
&mdash;
4955
@endif
5056
</td>
51-
<td class="text-right hidden-sm hidden-xs">
52-
@datetimeHuman($key->created_at)
53-
</td>
54-
<td class="text-center">
55-
<a href="#delete" class="text-danger" data-action="delete" data-attr="{{ $key->identifier }}"><i class="fa fa-trash"></i></a>
57+
<td>@datetimeHuman($key->created_at)</td>
58+
<td>
59+
<a href="#" data-action="revoke-key" data-attr="{{ $key->identifier }}">
60+
<i class="fa fa-trash-o text-danger"></i>
61+
</a>
5662
</td>
5763
</tr>
5864
@endforeach
59-
</tbody>
60-
</table>
65+
</table>
66+
</div>
6167
</div>
6268
</div>
6369
</div>
64-
</div>
6570
@endsection
6671

6772
@section('footer-scripts')
6873
@parent
6974
<script>
7075
$(document).ready(function() {
71-
$('[data-action="delete"]').click(function (event) {
76+
$(function () {
77+
$('[data-toggle="tooltip"]').tooltip()
78+
});
79+
$('.toggle-display').on('click', function () {
80+
$(this).parent().find('code[data-attr="api-key"]').removeClass('hidden');
81+
$(this).hide();
82+
});
83+
84+
$('[data-action="revoke-key"]').click(function (event) {
7285
var self = $(this);
7386
event.preventDefault();
7487
swal({

resources/themes/pterodactyl/base/api/new.blade.php

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,40 @@
88
<h1>@lang('base.api.new.header')<small>@lang('base.api.new.header_sub')</small></h1>
99
<ol class="breadcrumb">
1010
<li><a href="{{ route('index') }}">@lang('strings.home')</a></li>
11-
<li><a href="{{ route('account.api') }}">@lang('navigation.account.api_access')</a></li>
12-
<li class="active">@lang('strings.new')</li>
11+
<li class="active">@lang('navigation.account.api_access')</li>
12+
<li class="active">@lang('base.api.new.header')</li>
1313
</ol>
1414
@endsection
1515

16-
@section('footer-scripts')
17-
@parent
18-
<script type="text/javascript">
19-
$(document).ready(function () {
20-
$('#selectAllCheckboxes').on('click', function () {
21-
$('input[type=checkbox]').prop('checked', true);
22-
});
23-
$('#unselectAllCheckboxes').on('click', function () {
24-
$('input[type=checkbox]').prop('checked', false);
25-
});
26-
})
27-
</script>
28-
@endsection
29-
3016
@section('content')
31-
<form action="{{ route('account.api.new') }}" method="POST">
3217
<div class="row">
33-
<div class="col-xs-12">
34-
<div class="box">
35-
<div class="box-header with-border">
36-
<div class="box-title">@lang('base.api.new.form_title')</div>
37-
</div>
38-
<div class="box-body">
39-
<div class="row">
40-
<div class="form-group col-xs-12 col-lg-6">
41-
<label>@lang('base.api.new.descriptive_memo.title')</label>
42-
<input type="text" name="memo" class="form-control" name />
43-
<p class="help-block">@lang('base.api.new.descriptive_memo.description')</p>
44-
</div>
45-
<div class="form-group col-xs-12 col-lg-6">
46-
<label>@lang('base.api.new.allowed_ips.title')</label>
47-
<textarea name="allowed_ips" class="form-control" name></textarea>
48-
<p class="help-block">@lang('base.api.new.allowed_ips.description')</p>
18+
<form method="POST" action="{{ route('account.api.new') }}">
19+
<div class="col-sm-6 col-xs-12">
20+
<div class="box box-primary">
21+
<div class="box-body">
22+
<div class="form-group">
23+
<label class="control-label" for="memoField">Description <span class="field-required"></span></label>
24+
<input id="memoField" type="text" name="memo" class="form-control" value="{{ old('memo') }}">
4925
</div>
26+
<p class="text-muted">Set an easy to understand description for this API key to help you identify it later on.</p>
5027
</div>
51-
<div class="row">
52-
<div class="col-xs-12">
53-
{!! csrf_field() !!}
54-
<button class="btn btn-success pull-right">@lang('strings.create') &rarr;</button>
28+
</div>
29+
</div>
30+
<div class="col-sm-6 col-xs-12">
31+
<div class="box box-primary">
32+
<div class="box-body">
33+
<div class="form-group">
34+
<label class="control-label" for="allowedIps">Allowed Connection IPs <span class="field-optional"></span></label>
35+
<textarea id="allowedIps" name="allowed_ips" class="form-control" rows="5">{{ old('allowed_ips') }}</textarea>
5536
</div>
37+
<p class="text-muted">If you would like to limit this API key to specific IP addresses enter them above, one per line. CIDR notation is allowed for each IP address. Leave blank to allow any IP address.</p>
38+
</div>
39+
<div class="box-footer">
40+
{{ csrf_field() }}
41+
<button type="submit" class="btn btn-success btn-sm pull-right">Create</button>
5642
</div>
5743
</div>
5844
</div>
59-
</div>
45+
</form>
6046
</div>
61-
</form>
6247
@endsection

resources/themes/pterodactyl/layouts/master.blade.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@
101101
<i class="fa fa-lock"></i> <span>@lang('navigation.account.security_controls')</span>
102102
</a>
103103
</li>
104-
{{--<li class="{{ (Route::currentRouteName() !== 'account.api' && Route::currentRouteName() !== 'account.api.new') ?: 'active' }}">--}}
105-
{{--<a href="{{ route('account.api')}}">--}}
106-
{{--<i class="fa fa-code"></i> <span>@lang('navigation.account.api_access')</span>--}}
107-
{{--</a>--}}
108-
{{--</li>--}}
104+
<li class="{{ (Route::currentRouteName() !== 'account.api' && Route::currentRouteName() !== 'account.api.new') ?: 'active' }}">
105+
<a href="{{ route('account.api')}}">
106+
<i class="fa fa-code"></i> <span>@lang('navigation.account.api_access')</span>
107+
</a>
108+
</li>
109109
<li class="{{ Route::currentRouteName() !== 'index' ?: 'active' }}">
110110
<a href="{{ route('index')}}">
111111
<i class="fa fa-server"></i> <span>@lang('navigation.account.my_servers')</span>

routes/base.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,15 @@
3030
|
3131
| Endpoint: /account/api
3232
|
33-
| Temporarily Disabled
3433
*/
35-
//Route::group(['prefix' => 'account/api'], function () {
36-
// Route::get('/', 'AccountKeyController@index')->name('account.api');
37-
// Route::get('/new', 'AccountKeyController@create')->name('account.api.new');
38-
//
39-
// Route::post('/new', 'AccountKeyController@store');
40-
//
41-
// Route::delete('/revoke/{identifier}', 'AccountKeyController@revoke')->name('account.api.revoke');
42-
//});
34+
Route::group(['prefix' => 'account/api'], function () {
35+
Route::get('/', 'ClientApiController@index')->name('account.api');
36+
Route::get('/new', 'ClientApiController@create')->name('account.api.new');
37+
38+
Route::post('/new', 'ClientApiController@store');
39+
40+
Route::delete('/revoke/{identifier}', 'ClientApiController@delete')->name('account.api.revoke');
41+
});
4342

4443
/*
4544
|--------------------------------------------------------------------------

0 commit comments

Comments
 (0)