Basic implemention of multiple selectable images for an egg

The admin side of this is quite ugly when creating/editing a server, but I'm not putting effort into that right now with React Admin soon™
This commit is contained in:
Dane Everitt 2020-12-13 09:53:17 -08:00
parent 3e65a2d055
commit 78c4ac80bc
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
11 changed files with 123 additions and 31 deletions

View file

@ -111,17 +111,19 @@ class CreateServerController extends Controller
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
* @throws \Throwable
*/
public function store(ServerFormRequest $request)
{
$server = $this->creationService->handle(
$request->except(['_token'])
);
$data = $request->except(['_token']);
if (!empty($data['custom_image'])) {
$data['image'] = $data['custom_image'];
unset($data['custom_image']);
}
$server = $this->creationService->handle($data);
$this->alert->success(
trans('admin/server.alerts.server_created')

View file

@ -334,14 +334,19 @@ class ServersController extends Controller
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function saveStartup(Request $request, Server $server)
{
$data = $request->except('_token');
if (!empty($data['custom_docker_image'])) {
$data['docker_image'] = $data['custom_docker_image'];
unset($data['custom_docker_image']);
}
try {
$this->startupModificationService
->setUserLevel(User::USER_LEVEL_ADMIN)
->handle($server, $request->except('_token'));
->handle($server, $data);
} catch (DataValidationException $exception) {
throw new ValidationException($exception->validator);
}

View file

@ -1,11 +1,4 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* This software is licensed under the terms of the MIT license.
* https://opensource.org/licenses/MIT
*/
namespace Pterodactyl\Http\Requests\Admin;
@ -23,6 +16,7 @@ class ServerFormRequest extends AdminFormRequest
{
$rules = Server::getRules();
$rules['description'][] = 'nullable';
$rules['custom_image'] = 'sometimes|nullable|string';
return $rules;
}

View file

@ -10,7 +10,9 @@ namespace Pterodactyl\Models;
* @property string $name
* @property string|null $description
* @property array|null $features
* @property string $docker_image
* @property string $docker_image -- deprecated, use $docker_images
* @property string $update_url
* @property array $docker_images
* @property string|null $config_files
* @property string|null $config_startup
* @property string|null $config_logs
@ -76,7 +78,7 @@ class Egg extends Model
'name',
'description',
'features',
'docker_image',
'docker_images',
'config_files',
'config_startup',
'config_logs',
@ -101,6 +103,7 @@ class Egg extends Model
'script_is_privileged' => 'boolean',
'copy_script_from' => 'integer',
'features' => 'array',
'docker_images' => 'array',
];
/**
@ -113,13 +116,15 @@ class Egg extends Model
'description' => 'string|nullable',
'features' => 'array|nullable',
'author' => 'required|string|email',
'docker_image' => 'required|string|max:191',
'docker_images' => 'required|array|min:1',
'docker_images.*' => 'required|string',
'startup' => 'required|nullable|string',
'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id',
'config_stop' => 'required_without:config_from|nullable|string|max:191',
'config_startup' => 'required_without:config_from|nullable|json',
'config_logs' => 'required_without:config_from|nullable|json',
'config_files' => 'required_without:config_from|nullable|json',
'update_url' => 'sometimes|nullable|string',
];
/**
@ -131,6 +136,7 @@ class Egg extends Model
'config_startup' => null,
'config_logs' => null,
'config_files' => null,
'update_url' => null,
];
/**

View file

@ -38,13 +38,14 @@ class EggExporterService
'_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO',
'meta' => [
'version' => 'PTDL_v1',
'update_url' => $egg->update_url,
],
'exported_at' => Carbon::now()->toIso8601String(),
'name' => $egg->name,
'author' => $egg->author,
'description' => $egg->description,
'features' => $egg->features,
'image' => $egg->docker_image,
'images' => $egg->docker_images,
'startup' => $egg->startup,
'config' => [
'files' => $egg->inherit_config_files,

View file

@ -102,7 +102,10 @@ class EggImporterService
'name' => object_get($parsed, 'name'),
'description' => object_get($parsed, 'description'),
'features' => object_get($parsed, 'features'),
'docker_image' => object_get($parsed, 'image'),
// Maintain backwards compatability for eggs that are still using the old single image
// string format. New eggs can provide an array of Docker images that can be used.
'docker_images' => object_get($parsed, 'images') ?? [object_get($parsed, 'image')],
'update_url' => object_get($parsed, 'meta.update_url'),
'config_files' => object_get($parsed, 'config.files'),
'config_startup' => object_get($parsed, 'config.startup'),
'config_logs' => object_get($parsed, 'config.logs'),

View file

@ -45,7 +45,11 @@ class EggTransformer extends BaseTransformer
'nest' => $model->nest_id,
'author' => $model->author,
'description' => $model->description,
'docker_image' => $model->docker_image,
// "docker_image" is deprecated, but left here to avoid breaking too many things at once
// in external software. We'll remove it down the road once things have gotten the chance
// to upgrade to using "docker_images".
'docker_image' => count($model->docker_images) > 0 ? $model->docker_images[0] : '',
'docker_images' => $model->docker_images,
'config' => [
'files' => json_decode($model->config_files, true),
'startup' => json_decode($model->config_startup, true),

View file

@ -0,0 +1,51 @@
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SupportMultipleDockerImagesAndUpdates extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('eggs', function (Blueprint $table) {
$table->json('docker_images')->after('docker_image')->nullable();
$table->text('update_url')->after('docker_images')->nullable();
});
Schema::table('eggs', function (Blueprint $table) {
DB::statement('UPDATE `eggs` SET `docker_images` = JSON_ARRAY(docker_image)');
});
Schema::table('eggs', function (Blueprint $table) {
$table->dropColumn('docker_image');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('eggs', function (Blueprint $table) {
$table->text('docker_image')->after('docker_images');
});
Schema::table('eggs', function (Blueprint $table) {
DB::statement('UPDATE `eggs` SET `docker_image` = JSON_UNQUOTE(JSON_EXTRACT(docker_images, "$[0]"))');
});
Schema::table('eggs', function (Blueprint $table) {
$table->dropColumn('docker_images');
$table->dropColumn('update_url');
});
}
}

View file

@ -82,7 +82,13 @@ $('#pEggId').on('change', function (event) {
let parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null);
let objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null);
$('#pDefaultContainer').val(_.get(objectChain, 'docker_image', 'not defined!'));
const images = _.get(objectChain, 'docker_images', [])
for (let i = 0; i < images.length; i++) {
let opt = document.createElement('option');
opt.value = images[i];
opt.innerHTML = images[i];
$('#pDefaultContainer').append(opt);
}
if (!_.get(objectChain, 'startup', false)) {
$('#pStartup').val(_.get(parentChain, 'startup', 'ERROR: Startup Not Defined!'));

View file

@ -265,8 +265,9 @@
<div class="box-body row">
<div class="form-group col-xs-12">
<label for="pDefaultContainer">Docker Image</label>
<input id="pDefaultContainer" name="image" value="{{ old('image') }}" class="form-control" />
<p class="small text-muted no-margin">This is the default Docker image that will be used to run this server.</p>
<select id="pDefaultContainer" name="image" class="form-control"></select>
<input id="pDefaultContainerCustom" name="custom_image" value="{{ old('custom_image') }}" class="form-control" placeholder="Or enter a custom image..." style="margin-top:1rem"/>
<p class="small text-muted no-margin">This is the default Docker image that will be used to run this server. Select an image from the dropdown above, or enter a custom image in the text field above.</p>
</div>
</div>
</div>
@ -323,11 +324,14 @@
@endforeach
@endif
@endif
@if(old('image'))
$('#pDefaultContainer').val('{{ old('image') }}');
@endif
}
// END Persist 'Service Variables'
</script>
{!! Theme::js('js/admin/new-server.js?v=20201003') !!}
{!! Theme::js('js/admin/new-server.js?v=20201212') !!}
<script type="application/javascript">
$(document).ready(function() {

View file

@ -89,13 +89,14 @@
</div>
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Docker Container Configuration</h3>
<h3 class="box-title">Docker Image Configuration</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pDockerImage" class="control-label">Image</label>
<input type="text" name="docker_image" id="pDockerImage" value="{{ $server->image }}" class="form-control" />
<p class="text-muted small">The Docker image to use for this server. The default image for the selected egg is <code id="setDefaultImage"></code>.</p>
<label for="pDockerImage">Image</label>
<select id="pDockerImage" name="docker_image" class="form-control"></select>
<input id="pDockerImageCustom" name="custom_docker_image" value="{{ old('custom_docker_image') }}" class="form-control" placeholder="Or enter a custom image..." style="margin-top:1rem"/>
<p class="small text-muted no-margin">This is the Docker image that will be used to run this server. Select an image from the dropdown or enter a custom image in the text field above.</p>
</div>
</div>
</div>
@ -117,10 +118,25 @@
var parentChain = _.get(Pterodactyl.nests, $("#pNestId").val());
var objectChain = _.get(parentChain, 'eggs.' + selectedEgg);
$('#setDefaultImage').html(_.get(objectChain, 'docker_image', 'undefined'));
$('#pDockerImage').val(_.get(objectChain, 'docker_image', 'undefined'));
$('#setDefaultImage').html(_.get(objectChain, 'docker_images.0', 'undefined'));
const images = _.get(objectChain, 'docker_images', [])
for (let i = 0; i < images.length; i++) {
let opt = document.createElement('option');
opt.value = images[i];
opt.innerHTML = images[i];
if (objectChain.id === parseInt(Pterodactyl.server.egg_id) && Pterodactyl.server.image == opt.value) {
opt.checked = true
}
$('#pDockerImage').append(opt);
}
$('#pDockerImage').on('change', function () {
$('#pDockerImageCustom').val('');
})
if (objectChain.id === parseInt(Pterodactyl.server.egg_id)) {
$('#pDockerImage').val(Pterodactyl.server.image);
if ($('#pDockerImage').val() != Pterodactyl.server.image) {
$('#pDockerImageCustom').val(Pterodactyl.server.image);
}
}
if (!_.get(objectChain, 'startup', false)) {