Begin updating UI

This commit is contained in:
Dane Everitt 2017-10-02 22:51:13 -05:00
parent 493c5888a3
commit ae671e6b19
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
22 changed files with 182 additions and 102 deletions

View file

@ -32,6 +32,4 @@ QUEUE_HIGH=high
QUEUE_STANDARD=standard QUEUE_STANDARD=standard
QUEUE_LOW=low QUEUE_LOW=low
SQS_KEY=aws-public SERVICE_AUTHOR=undefined@unknown-author.com
SQS_SECRET=aws-secret
SQS_QUEUE_PREFIX=aws-queue-prefix

View file

@ -9,7 +9,6 @@
namespace Pterodactyl\Console\Commands\Environment; namespace Pterodactyl\Console\Commands\Environment;
use Ramsey\Uuid\Uuid;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel; use Illuminate\Contracts\Console\Kernel;
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
@ -38,6 +37,7 @@ class AppSettingsCommand extends Command
* @var string * @var string
*/ */
protected $signature = 'p:environment:setup protected $signature = 'p:environment:setup
{--author= : The email that services created on this instance should be linked to.}
{--url= : The URL that this Panel is running on.} {--url= : The URL that this Panel is running on.}
{--timezone= : The timezone to use for Panel times.} {--timezone= : The timezone to use for Panel times.}
{--cache= : The cache driver backend to use.} {--cache= : The cache driver backend to use.}
@ -72,9 +72,10 @@ class AppSettingsCommand extends Command
*/ */
public function handle() public function handle()
{ {
if (is_null($this->config->get('pterodactyl.service.author'))) { $this->output->comment(trans('command/messages.environment.app.author_help'));
$this->variables['SERVICE_AUTHOR'] = Uuid::uuid4()->toString(); $this->variables['SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask(
} trans('command/messages.environment.app.author'), $this->config->get('pterodactyl.service.author', 'undefined@unknown-author.com')
);
$this->output->comment(trans('command/messages.environment.app.app_url_help')); $this->output->comment(trans('command/messages.environment.app.app_url_help'));
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask( $this->variables['APP_URL'] = $this->option('url') ?? $this->ask(

View file

@ -9,6 +9,9 @@
namespace Pterodactyl\Contracts\Repository; namespace Pterodactyl\Contracts\Repository;
use Pterodactyl\Models\Service;
use Illuminate\Support\Collection;
interface ServiceRepositoryInterface extends RepositoryInterface interface ServiceRepositoryInterface extends RepositoryInterface
{ {
/** /**
@ -17,7 +20,15 @@ interface ServiceRepositoryInterface extends RepositoryInterface
* @param int $id * @param int $id
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function getWithOptions($id = null); public function getWithOptions(int $id = null): Collection;
/**
* Return a service or all services and the count of options, packs, and servers for that service.
*
* @param int|null $id
* @return \Illuminate\Support\Collection
*/
public function getWithCounts(int $id = null): Collection;
/** /**
* Return a service along with its associated options and the servers relation on those options. * Return a service along with its associated options and the servers relation on those options.
@ -25,5 +36,5 @@ interface ServiceRepositoryInterface extends RepositoryInterface
* @param int $id * @param int $id
* @return mixed * @return mixed
*/ */
public function getWithOptionServers($id); public function getWithOptionServers(int $id): Service;
} }

View file

@ -9,7 +9,9 @@
namespace Pterodactyl\Http\Controllers\Admin; namespace Pterodactyl\Http\Controllers\Admin;
use Illuminate\View\View;
use Pterodactyl\Models\Service; use Pterodactyl\Models\Service;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Services\ServiceUpdateService; use Pterodactyl\Services\Services\ServiceUpdateService;
@ -46,6 +48,15 @@ class ServiceController extends Controller
*/ */
protected $updateService; protected $updateService;
/**
* ServiceController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Services\ServiceCreationService $creationService
* @param \Pterodactyl\Services\Services\ServiceDeletionService $deletionService
* @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository
* @param \Pterodactyl\Services\Services\ServiceUpdateService $updateService
*/
public function __construct( public function __construct(
AlertsMessageBag $alert, AlertsMessageBag $alert,
ServiceCreationService $creationService, ServiceCreationService $creationService,
@ -65,10 +76,10 @@ class ServiceController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function index() public function index(): View
{ {
return view('admin.services.index', [ return view('admin.services.index', [
'services' => $this->repository->getWithOptions(), 'services' => $this->repository->getWithCounts(),
]); ]);
} }
@ -77,7 +88,7 @@ class ServiceController extends Controller
* *
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function create() public function create(): View
{ {
return view('admin.services.new'); return view('admin.services.new');
} }
@ -88,7 +99,7 @@ class ServiceController extends Controller
* @param int $service * @param int $service
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function view($service) public function view(int $service): View
{ {
return view('admin.services.view', [ return view('admin.services.view', [
'service' => $this->repository->getWithOptionServers($service), 'service' => $this->repository->getWithOptionServers($service),
@ -101,7 +112,7 @@ class ServiceController extends Controller
* @param \Pterodactyl\Models\Service $service * @param \Pterodactyl\Models\Service $service
* @return \Illuminate\View\View * @return \Illuminate\View\View
*/ */
public function viewFunctions(Service $service) public function viewFunctions(Service $service): View
{ {
return view('admin.services.functions', ['service' => $service]); return view('admin.services.functions', ['service' => $service]);
} }
@ -114,7 +125,7 @@ class ServiceController extends Controller
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function store(ServiceFormRequest $request) public function store(ServiceFormRequest $request): RedirectResponse
{ {
$service = $this->creationService->handle($request->normalize()); $service = $this->creationService->handle($request->normalize());
$this->alert->success(trans('admin/services.notices.service_created', ['name' => $service->name]))->flash(); $this->alert->success(trans('admin/services.notices.service_created', ['name' => $service->name]))->flash();
@ -132,7 +143,7 @@ class ServiceController extends Controller
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function update(ServiceFormRequest $request, Service $service) public function update(ServiceFormRequest $request, Service $service): RedirectResponse
{ {
$this->updateService->handle($service->id, $request->normalize()); $this->updateService->handle($service->id, $request->normalize());
$this->alert->success(trans('admin/services.notices.service_updated'))->flash(); $this->alert->success(trans('admin/services.notices.service_updated'))->flash();
@ -150,7 +161,7 @@ class ServiceController extends Controller
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function updateFunctions(ServiceFunctionsFormRequest $request, Service $service) public function updateFunctions(ServiceFunctionsFormRequest $request, Service $service): RedirectResponse
{ {
$this->updateService->handle($service->id, $request->normalize()); $this->updateService->handle($service->id, $request->normalize());
$this->alert->success(trans('admin/services.notices.functions_updated'))->flash(); $this->alert->success(trans('admin/services.notices.functions_updated'))->flash();
@ -166,7 +177,7 @@ class ServiceController extends Controller
* *
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/ */
public function destroy(Service $service) public function destroy(Service $service): RedirectResponse
{ {
$this->deletionService->handle($service->id); $this->deletionService->handle($service->id);
$this->alert->success(trans('admin/services.notices.service_deleted'))->flash(); $this->alert->success(trans('admin/services.notices.service_deleted'))->flash();

View file

@ -13,7 +13,12 @@ use Illuminate\Foundation\Http\FormRequest;
abstract class AdminFormRequest extends FormRequest abstract class AdminFormRequest extends FormRequest
{ {
abstract public function rules(); /**
* The rules to apply to the incoming form request.
*
* @return array
*/
abstract public function rules(): array;
/** /**
* Determine if the user is an admin and has permission to access this * Determine if the user is an admin and has permission to access this
@ -21,7 +26,7 @@ abstract class AdminFormRequest extends FormRequest
* *
* @return bool * @return bool
*/ */
public function authorize() public function authorize(): bool
{ {
if (is_null($this->user())) { if (is_null($this->user())) {
return false; return false;
@ -37,7 +42,7 @@ abstract class AdminFormRequest extends FormRequest
* @param array $only * @param array $only
* @return array * @return array
*/ */
public function normalize($only = []) public function normalize($only = []): array
{ {
return array_merge( return array_merge(
$this->only($only), $this->only($only),

View file

@ -16,22 +16,12 @@ class ServiceFormRequest extends AdminFormRequest
/** /**
* @return array * @return array
*/ */
public function rules() public function rules(): array
{ {
$rules = [ return [
'name' => 'required|string|min:1|max:255', 'name' => 'required|string|min:1|max:255',
'description' => 'required|nullable|string', 'description' => 'required|nullable|string',
'folder' => 'required|regex:/^[\w.-]{1,50}$/|unique:services,folder',
'startup' => 'required|nullable|string', 'startup' => 'required|nullable|string',
]; ];
if ($this->method() === 'PATCH') {
$service = $this->route()->parameter('service');
$rules['folder'] = $rules['folder'] . ',' . $service->id;
return $rules;
}
return $rules;
} }
} }

View file

@ -31,7 +31,12 @@ class Service extends Model implements CleansAttributes, ValidableContract
* *
* @var array * @var array
*/ */
protected $fillable = ['name', 'author', 'description', 'folder', 'startup', 'index_file']; protected $fillable = [
'name',
'description',
'startup',
'index_file',
];
/** /**
* @var array * @var array
@ -40,7 +45,6 @@ class Service extends Model implements CleansAttributes, ValidableContract
'author' => 'required', 'author' => 'required',
'name' => 'required', 'name' => 'required',
'description' => 'sometimes', 'description' => 'sometimes',
'folder' => 'required',
'startup' => 'sometimes', 'startup' => 'sometimes',
'index_file' => 'required', 'index_file' => 'required',
]; ];
@ -49,10 +53,9 @@ class Service extends Model implements CleansAttributes, ValidableContract
* @var array * @var array
*/ */
protected static $dataIntegrityRules = [ protected static $dataIntegrityRules = [
'author' => 'string|size:36', 'author' => 'email',
'name' => 'string|max:255', 'name' => 'string|max:255',
'description' => 'nullable|string', 'description' => 'nullable|string',
'folder' => 'string|max:255|regex:/^[\w.-]{1,50}$/|unique:services,folder',
'startup' => 'nullable|string', 'startup' => 'nullable|string',
'index_file' => 'string', 'index_file' => 'string',
]; ];
@ -74,12 +77,7 @@ class Service extends Model implements CleansAttributes, ValidableContract
*/ */
public function packs() public function packs()
{ {
return $this->hasManyThrough( return $this->hasManyThrough(Pack::class, ServiceOption::class, 'service_id', 'option_id');
Pack::class,
ServiceOption::class,
'service_id',
'option_id'
);
} }
/** /**

View file

@ -31,7 +31,22 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
* *
* @var array * @var array
*/ */
protected $guarded = ['id', 'created_at', 'updated_at']; protected $fillable = [
'name',
'description',
'docker_image',
'config_files',
'config_startup',
'config_logs',
'config_stop',
'config_from',
'startup',
'script_is_privileged',
'script_install',
'script_entry',
'script_container',
'copy_script_from',
];
/** /**
* Cast values to correct type. * Cast values to correct type.
@ -40,7 +55,9 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
*/ */
protected $casts = [ protected $casts = [
'service_id' => 'integer', 'service_id' => 'integer',
'config_from' => 'integer',
'script_is_privileged' => 'boolean', 'script_is_privileged' => 'boolean',
'copy_script_from' => 'integer',
]; ];
/** /**
@ -48,6 +65,7 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
*/ */
protected static $applicationRules = [ protected static $applicationRules = [
'service_id' => 'required', 'service_id' => 'required',
'author' => 'required',
'name' => 'required', 'name' => 'required',
'description' => 'required', 'description' => 'required',
'tag' => 'required', 'tag' => 'required',
@ -64,13 +82,14 @@ class ServiceOption extends Model implements CleansAttributes, ValidableContract
* @var array * @var array
*/ */
protected static $dataIntegrityRules = [ protected static $dataIntegrityRules = [
'service_id' => 'numeric|exists:services,id', 'service_id' => 'bail|numeric|exists:services,id',
'author' => 'email',
'name' => 'string|max:255', 'name' => 'string|max:255',
'description' => 'string', 'description' => 'string',
'tag' => 'alpha_num|max:60|unique:service_options,tag', 'tag' => 'bail|alpha_num|max:60|unique:service_options,tag',
'docker_image' => 'string|max:255', 'docker_image' => 'string|max:255',
'startup' => 'nullable|string', 'startup' => 'nullable|string',
'config_from' => 'nullable|numeric|exists:service_options,id', 'config_from' => 'bail|nullable|numeric|exists:service_options,id',
'config_stop' => 'nullable|string|max:255', 'config_stop' => 'nullable|string|max:255',
'config_startup' => 'nullable|json', 'config_startup' => 'nullable|json',
'config_logs' => 'nullable|json', 'config_logs' => 'nullable|json',

View file

@ -9,8 +9,8 @@
namespace Pterodactyl\Repositories\Eloquent; namespace Pterodactyl\Repositories\Eloquent;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Service; use Pterodactyl\Models\Service;
use Illuminate\Support\Collection;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
@ -27,16 +27,14 @@ class ServiceRepository extends EloquentRepository implements ServiceRepositoryI
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getWithOptions($id = null) public function getWithOptions(int $id = null): Collection
{ {
Assert::nullOrNumeric($id, 'First argument passed to getWithOptions must be null or numeric, received %s.');
$instance = $this->getBuilder()->with('options.packs', 'options.variables'); $instance = $this->getBuilder()->with('options.packs', 'options.variables');
if (! is_null($id)) { if (! is_null($id)) {
$instance = $instance->find($id, $this->getColumns()); $instance = $instance->find($id, $this->getColumns());
if (! $instance) { if (! $instance) {
throw new RecordNotFoundException(); throw new RecordNotFoundException;
} }
return $instance; return $instance;
@ -48,15 +46,33 @@ class ServiceRepository extends EloquentRepository implements ServiceRepositoryI
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getWithOptionServers($id) public function getWithCounts(int $id = null): Collection
{ {
Assert::numeric($id, 'First argument passed to getWithOptionServers must be numeric, received %s.'); $instance = $this->getBuilder()->withCount(['options', 'packs', 'servers']);
$instance = $this->getBuilder()->with('options.servers')->find($id, $this->getColumns()); if (! is_null($id)) {
$instance = $instance->find($id, $this->getColumns());
if (! $instance) { if (! $instance) {
throw new RecordNotFoundException(); throw new RecordNotFoundException;
} }
return $instance; return $instance;
} }
return $instance->get($this->getColumns());
}
/**
* {@inheritdoc}
*/
public function getWithOptionServers(int $id): Service
{
$instance = $this->getBuilder()->with('options.servers')->find($id, $this->getColumns());
if (! $instance) {
throw new RecordNotFoundException;
}
/* @var Service $instance */
return $instance;
}
} }

View file

@ -40,7 +40,7 @@ class InstallScriptUpdateService
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException * @throws \Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException
*/ */
public function handle($option, array $data) public function handle($option, array $data): void
{ {
if (! $option instanceof ServiceOption) { if (! $option instanceof ServiceOption) {
$option = $this->repository->find($option); $option = $this->repository->find($option);

View file

@ -9,11 +9,19 @@
namespace Pterodactyl\Services\Services\Options; namespace Pterodactyl\Services\Services\Options;
use Ramsey\Uuid\Uuid;
use Pterodactyl\Models\ServiceOption;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException;
class OptionCreationService class OptionCreationService
{ {
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/** /**
* @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
*/ */
@ -22,10 +30,12 @@ class OptionCreationService
/** /**
* CreationService constructor. * CreationService constructor.
* *
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository
*/ */
public function __construct(ServiceOptionRepositoryInterface $repository) public function __construct(ConfigRepository $config, ServiceOptionRepositoryInterface $repository)
{ {
$this->config = $config;
$this->repository = $repository; $this->repository = $repository;
} }
@ -38,7 +48,7 @@ class OptionCreationService
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException * @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException
*/ */
public function handle(array $data) public function handle(array $data): ServiceOption
{ {
if (! is_null(array_get($data, 'config_from'))) { if (! is_null(array_get($data, 'config_from'))) {
$results = $this->repository->findCountWhere([ $results = $this->repository->findCountWhere([
@ -53,6 +63,14 @@ class OptionCreationService
$data['config_from'] = null; $data['config_from'] = null;
} }
return $this->repository->create($data); if (count($parts = explode(':', array_get($data, 'tag'))) > 1) {
$data['tag'] = $this->config->get('pterodactyl.service.author') . ':' . trim(array_pop($parts));
} else {
$data['tag'] = $this->config->get('pterodactyl.service.author') . ':' . trim(array_get($data, 'tag'));
}
return $this->repository->create(array_merge($data, [
'uuid' => Uuid::uuid4()->toString(),
]), true, true);
} }
} }

View file

@ -9,7 +9,6 @@
namespace Pterodactyl\Services\Services\Options; namespace Pterodactyl\Services\Services\Options;
use Webmozart\Assert\Assert;
use Pterodactyl\Exceptions\Service\HasActiveServersException; use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
@ -50,10 +49,8 @@ class OptionDeletionService
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\HasChildrenException * @throws \Pterodactyl\Exceptions\Service\ServiceOption\HasChildrenException
*/ */
public function handle($option) public function handle(int $option): int
{ {
Assert::integerish($option, 'First argument passed to handle must be integer, received %s.');
$servers = $this->serverRepository->findCountWhere([['option_id', '=', $option]]); $servers = $this->serverRepository->findCountWhere([['option_id', '=', $option]]);
if ($servers > 0) { if ($servers > 0) {
throw new HasActiveServersException(trans('exceptions.service.options.delete_has_servers')); throw new HasActiveServersException(trans('exceptions.service.options.delete_has_servers'));

View file

@ -40,7 +40,7 @@ class OptionUpdateService
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException * @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException
*/ */
public function handle($option, array $data) public function handle($option, array $data): void
{ {
if (! $option instanceof ServiceOption) { if (! $option instanceof ServiceOption) {
$option = $this->repository->find($option); $option = $this->repository->find($option);

View file

@ -9,6 +9,8 @@
namespace Pterodactyl\Services\Services; namespace Pterodactyl\Services\Services;
use Ramsey\Uuid\Uuid;
use Pterodactyl\Models\Service;
use Pterodactyl\Traits\Services\CreatesServiceIndex; use Pterodactyl\Traits\Services\CreatesServiceIndex;
use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
@ -49,16 +51,15 @@ class ServiceCreationService
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function handle(array $data) public function handle(array $data): Service
{ {
return $this->repository->create(array_merge([ return $this->repository->create([
'uuid' => Uuid::uuid4()->toString(),
'author' => $this->config->get('pterodactyl.service.author'), 'author' => $this->config->get('pterodactyl.service.author'),
], [
'name' => array_get($data, 'name'), 'name' => array_get($data, 'name'),
'description' => array_get($data, 'description'), 'description' => array_get($data, 'description'),
'folder' => array_get($data, 'folder'),
'startup' => array_get($data, 'startup'), 'startup' => array_get($data, 'startup'),
'index_file' => $this->getIndexScript(), 'index_file' => $this->getIndexScript(),
])); ], true, true);
} }
} }

View file

@ -47,7 +47,7 @@ class ServiceDeletionService
* *
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/ */
public function handle($service) public function handle(int $service): int
{ {
$count = $this->serverRepository->findCountWhere([['service_id', '=', $service]]); $count = $this->serverRepository->findCountWhere([['service_id', '=', $service]]);
if ($count > 0) { if ($count > 0) {

View file

@ -36,7 +36,7 @@ class ServiceUpdateService
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function handle($service, array $data) public function handle(int $service, array $data): void
{ {
if (! is_null(array_get($data, 'author'))) { if (! is_null(array_get($data, 'author'))) {
unset($data['author']); unset($data['author']);

View file

@ -18,6 +18,13 @@
@endsection @endsection
@section('content') @section('content')
<div class="row">
<div class="col-xs-12">
<div class="alert alert-danger">
Services are a powerful feature of Pterodactyl Panel that allow for extreme flexibility and configuration. Please note that while powerful, modifing a service wrongly can very easily brick your servers and cause more problems. Please avoid editing our default services those provided by <code>support@pterodactyl.io</code> unless you are absolutely sure of what you are doing.
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<div class="box"> <div class="box">
@ -38,7 +45,7 @@
</tr> </tr>
@foreach($services as $service) @foreach($services as $service)
<tr> <tr>
<td class="middle"><a href="{{ route('admin.services.view', $service->id) }}">{{ $service->name }}</a></td> <td class="middle"><a href="{{ route('admin.services.view', $service->id) }}" data-toggle="tooltip" data-placement="right" title="{{ $service->author }}">{{ $service->name }}</a></td>
<td class="col-xs-6 middle">{{ $service->description }}</td> <td class="col-xs-6 middle">{{ $service->description }}</td>
<td class="text-center middle"><code>{{ $service->options_count }}</code></td> <td class="text-center middle"><code>{{ $service->options_count }}</code></td>
<td class="text-center middle"><code>{{ $service->packs_count }}</code></td> <td class="text-center middle"><code>{{ $service->packs_count }}</code></td>

View file

@ -46,13 +46,6 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="box"> <div class="box">
<div class="box-body"> <div class="box-body">
<div class="form-group">
<label class="control-label">Folder Name</label>
<div>
<input type="text" name="folder" class="form-control" value="{{ old('folder') }}" />
<p class="text-muted"><small>Service are downloaded by the daemon and stored in a folder using this name. The storage location is <code>/srv/daemon/services/{NAME}</code> by default.</small></p>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Default Start Command</label> <label class="control-label">Default Start Command</label>
<div> <div>

View file

@ -33,7 +33,7 @@
<label for="pServiceId" class="form-label">Associated Service</label> <label for="pServiceId" class="form-label">Associated Service</label>
<select name="service_id" id="pServiceId"> <select name="service_id" id="pServiceId">
@foreach($services as $service) @foreach($services as $service)
<option value="{{ $service->id }}" {{ old('service_id') != $service->id ?: 'selected' }}>{{ $service->name }}</option> <option value="{{ $service->id }}" {{ old('service_id') != $service->id ?: 'selected' }}>{{ $service->name }} &lt;{{ $service->author }}&gt;</option>
@endforeach @endforeach
</select> </select>
</div> </div>
@ -51,16 +51,19 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<label for="pTag" class="form-label">Option Tag</label> <label for="pTag" class="form-label">Option Tag</label>
<div class="input-group">
<span class="input-group-addon">{{ config('pterodactyl.service.author') }}:</span>
<input type="text" id="pTag" name="tag" value="{{ old('tag') }}" class="form-control" /> <input type="text" id="pTag" name="tag" value="{{ old('tag') }}" class="form-control" />
</div>
<p class="text-muted small">This should be a unique identifer for this service option that is not used for any other service options. Must be alpha-numeric and no more than 60 characters in length.</p> <p class="text-muted small">This should be a unique identifer for this service option that is not used for any other service options. Must be alpha-numeric and no more than 60 characters in length.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="pDockerImage" class="form-label">Docker Image</label> <label for="pDockerImage" class="control-label">Docker Image <span class="field-optional"></span></label>
<input type="text" id="pDockerImage" name="docker_image" value="{{ old('docker_image') }}" placeholder="quay.io/pterodactyl/service" class="form-control" /> <input type="text" id="pDockerImage" name="docker_image" value="{{ old('docker_image') }}" placeholder="quay.io/pterodactyl/service" class="form-control" />
<p class="text-muted small">The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.</p> <p class="text-muted small">The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="pStartup" class="form-label">Startup Command</label> <label for="pStartup" class="control-label">Startup Command <span class="field-optional"></span></label>
<textarea id="pStartup" name="startup" class="form-control" rows="4">{{ old('startup') }}</textarea> <textarea id="pStartup" name="startup" class="form-control" rows="4">{{ old('startup') }}</textarea>
<p class="text-muted small">The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.</p> <p class="text-muted small">The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.</p>
</div> </div>
@ -136,7 +139,7 @@
data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) { data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) {
return { return {
id: item.id, id: item.id,
text: item.name, text: item.name + ' <' + item.tag + '>',
}; };
}), }),
}); });

View file

@ -59,17 +59,16 @@
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<label for="pTag" class="form-label">Option Tag</label> <label class="form-label">Option Tag</label>
<input type="text" id="pTag" name="tag" value="{{ $option->tag }}" class="form-control" /> <input type="text" disabled value="{{ $option->tag }}" class="form-control" />
<p class="text-muted small">This should be a unique identifer for this service option that is not used for any other service options.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="pDockerImage" class="form-label">Docker Image</label> <label for="pDockerImage" class="control-label">Docker Image <span class="field-optional"></label>
<input type="text" id="pDockerImage" name="docker_image" value="{{ $option->docker_image }}" class="form-control" /> <input type="text" id="pDockerImage" name="docker_image" value="{{ $option->docker_image }}" class="form-control" />
<p class="text-muted small">The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.</p> <p class="text-muted small">The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.</p>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="pStartup" class="form-label">Startup Command</label> <label for="pStartup" class="control-label">Startup Command <span class="field-optional"></label>
<textarea id="pStartup" name="startup" class="form-control" rows="4" placeholder="{{ $option->service->startup }}">{{ $option->startup }}</textarea> <textarea id="pStartup" name="startup" class="form-control" rows="4" placeholder="{{ $option->service->startup }}">{{ $option->startup }}</textarea>
<p class="text-muted small">The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.</p> <p class="text-muted small">The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.</p>
</div> </div>
@ -97,7 +96,7 @@
<select name="config_from" id="pConfigFrom" class="form-control"> <select name="config_from" id="pConfigFrom" class="form-control">
<option value="0">None</option> <option value="0">None</option>
@foreach($option->service->options as $o) @foreach($option->service->options as $o)
<option value="{{ $o->id }}" {{ ($option->config_from !== $o->id) ?: 'selected' }}>{{ $o->name }}</option> <option value="{{ $o->id }}" {{ ($option->config_from !== $o->id) ?: 'selected' }}>{{ $o->name }} &lt;{{ $option->tag }}&gt;</option>
@endforeach @endforeach
</select> </select>
<p class="text-muted small">If you would like to default to settings from another option select the option from the menu above.</p> <p class="text-muted small">If you would like to default to settings from another option select the option from the menu above.</p>

View file

@ -44,22 +44,18 @@
<div class="form-group"> <div class="form-group">
<label class="control-label">Description</label> <label class="control-label">Description</label>
<div> <div>
<textarea name="description" class="form-control" rows="6">{{ $service->description }}</textarea> <textarea name="description" class="form-control" rows="7">{{ $service->description }}</textarea>
</div> </div>
</div> </div>
</div> </div>
<div class="box-footer">
<button id="deleteButton" type="submit" name="_method" value="DELETE" class="btn btn-sm btn-danger muted muted-hover"><i class="fa fa-trash-o"></i></button>
</div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="box"> <div class="box">
<div class="box-body"> <div class="box-body">
<div class="form-group">
<label class="control-label">Folder Name</label>
<div>
<input type="text" name="folder" class="form-control" value="{{ $service->folder }}" />
<p class="text-muted"><small>Service are downloaded by the daemon and stored in a folder using this name. The storage location is <code>/srv/daemon/services/{NAME}</code> by default.</small></p>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Default Start Command</label> <label class="control-label">Default Start Command</label>
<div> <div>
@ -67,10 +63,21 @@
<p class="text-muted"><small>The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.</small></p> <p class="text-muted"><small>The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.</small></p>
</div> </div>
</div> </div>
<div class="form-group">
<label class="control-label">Author</label>
<div>
<input type="text" readonly class="form-control" value="{{ $service->author }}" />
</div>
</div>
<div class="form-group">
<label class="control-label">UUID</label>
<div>
<input type="text" readonly class="form-control" value="{{ $service->uuid }}" />
</div>
</div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
{!! csrf_field() !!} {!! csrf_field() !!}
<button id="deleteButton" type="submit" name="_method" value="DELETE" class="btn btn-sm btn-danger muted muted-hover"><i class="fa fa-trash-o"></i></button>
<button type="submit" name="_method" value="PATCH" class="btn btn-primary btn-sm pull-right">Edit Service</button> <button type="submit" name="_method" value="PATCH" class="btn btn-primary btn-sm pull-right">Edit Service</button>
</div> </div>
</div> </div>
@ -86,7 +93,7 @@
<div class="box-body table-responsive no-padding"> <div class="box-body table-responsive no-padding">
<table class="table table-hover"> <table class="table table-hover">
<tr> <tr>
<th class="col-sm-4 col-md-3">Name</th> <th>Name</th>
<th>Description</th> <th>Description</th>
<th>Tag</th> <th>Tag</th>
<th class="text-center">Servers</th> <th class="text-center">Servers</th>

View file

@ -197,6 +197,12 @@
}); });
</script> </script>
@endif @endif
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
@show @show
</body> </body>
</html> </html>