Begin implementation of services for services/service options

This commit is contained in:
Dane Everitt 2017-08-08 23:24:55 -05:00
parent 7277f728a9
commit 2c77d5c44d
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
21 changed files with 567 additions and 408 deletions

View File

@ -26,6 +26,13 @@ namespace Pterodactyl\Contracts\Repository;
interface DatabaseHostRepositoryInterface extends RepositoryInterface
{
/**
* Return database hosts with a count of databases and the node information for which it is attached.
*
* @return \Illuminate\Support\Collection
*/
public function getWithViewDetails();
/**
* Delete a database host from the DB if there are no databases using it.
*

View File

@ -44,7 +44,14 @@ interface LocationRepositoryInterface extends RepositoryInterface, SearchableInt
*
* @return mixed
*/
public function allWithDetails();
public function getAllWithDetails();
/**
* Return all of the available locations with the nodes as a relationship.
*
* @return \Illuminate\Support\Collection
*/
public function getAllWithNodes();
/**
* Return all of the nodes and their respective count of servers for a location.

View File

@ -0,0 +1,30 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Contracts\Repository;
interface ServiceOptionRepositoryInterface extends RepositoryInterface
{
//
}

View File

@ -24,12 +24,13 @@
namespace Pterodactyl\Http\Controllers\Admin;
use Pterodactyl\Models\Location;
use Pterodactyl\Models\DatabaseHost;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Database\DatabaseHostService;
use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
class DatabaseController extends Controller
{
@ -39,14 +40,14 @@ class DatabaseController extends Controller
protected $alert;
/**
* @var \Pterodactyl\Models\DatabaseHost
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
protected $hostModel;
protected $locationRepository;
/**
* @var \Pterodactyl\Models\Location
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface
*/
protected $locationModel;
protected $repository;
/**
* @var \Pterodactyl\Services\Database\DatabaseHostService
@ -56,21 +57,21 @@ class DatabaseController extends Controller
/**
* DatabaseController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Models\DatabaseHost $hostModel
* @param \Pterodactyl\Models\Location $locationModel
* @param \Pterodactyl\Services\Database\DatabaseHostService $service
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $repository
* @param \Pterodactyl\Services\Database\DatabaseHostService $service
* @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository
*/
public function __construct(
AlertsMessageBag $alert,
DatabaseHost $hostModel,
Location $locationModel,
DatabaseHostService $service
DatabaseHostRepositoryInterface $repository,
DatabaseHostService $service,
LocationRepositoryInterface $locationRepository
) {
$this->alert = $alert;
$this->hostModel = $hostModel;
$this->locationModel = $locationModel;
$this->repository = $repository;
$this->service = $service;
$this->locationRepository = $locationRepository;
}
/**
@ -81,8 +82,8 @@ class DatabaseController extends Controller
public function index()
{
return view('admin.databases.index', [
'locations' => $this->locationModel->with('nodes')->get(),
'hosts' => $this->hostModel->withCount('databases')->with('node')->get(),
'locations' => $this->locationRepository->getAllWithNodes(),
'hosts' => $this->repository->getWithViewDetails(),
]);
}
@ -97,7 +98,7 @@ class DatabaseController extends Controller
$host->load('databases.server');
return view('admin.databases.view', [
'locations' => $this->locationModel->with('nodes')->get(),
'locations' => $this->locationRepository->getAllWithNodes(),
'host' => $host,
]);
}

View File

@ -74,7 +74,7 @@ class LocationController extends Controller
public function index()
{
return view('admin.locations.index', [
'locations' => $this->repository->allWithDetails(),
'locations' => $this->repository->getAllWithDetails(),
]);
}

View File

@ -26,6 +26,12 @@ namespace Pterodactyl\Http\Controllers\Admin;
use Log;
use Alert;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest;
use Pterodactyl\Http\Requests\Admin\ServiceOptionFormRequest;
use Pterodactyl\Services\Services\Options\CreationService;
use Pterodactyl\Services\Services\Variables\VariableCreationService;
use Route;
use Javascript;
use Illuminate\Http\Request;
@ -42,100 +48,95 @@ use Pterodactyl\Http\Requests\Admin\Service\StoreOptionVariable;
class OptionController extends Controller
{
/**
* Store the repository instance.
*
* @var \Pterodactyl\Repositories\OptionRepository
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $repository;
protected $alert;
/**
* @var \Pterodactyl\Services\Services\Options\CreationService
*/
protected $creationService;
/**
* @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface
*/
protected $serviceRepository;
/**
* @var \Pterodactyl\Services\Services\Variables\VariableCreationService
*/
protected $variableCreationService;
/**
* OptionController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository
* @param \Pterodactyl\Services\Services\Options\CreationService $creationService
* @param \Pterodactyl\Services\Services\Variables\VariableCreationService $variableCreationService
*/
public function __construct()
{
$this->repository = new OptionRepository(Route::current()->parameter('option'));
public function __construct(
AlertsMessageBag $alert,
ServiceRepositoryInterface $serviceRepository,
CreationService $creationService,
VariableCreationService $variableCreationService
) {
$this->alert = $alert;
$this->creationService = $creationService;
$this->serviceRepository = $serviceRepository;
$this->variableCreationService = $variableCreationService;
}
/**
* Handles request to view page for adding new option.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function create(Request $request)
public function create()
{
$services = Service::with('options')->get();
$services = $this->serviceRepository->getWithOptions();
Javascript::put(['services' => $services->keyBy('id')]);
return view('admin.services.options.new', ['services' => $services]);
}
/**
* Handles POST request to create a new option.
* Handle adding a new service option.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Response\RedirectResponse
* @param \Pterodactyl\Http\Requests\Admin\ServiceOptionFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(Request $request)
public function store(ServiceOptionFormRequest $request)
{
$repo = new OptionRepository;
$option = $this->creationService->handle($request->normalize());
$this->alert->success(trans('admin/services.options.notices.option_created'))->flash();
try {
$option = $repo->create($request->intersect([
'service_id', 'name', 'description', 'tag',
'docker_image', 'startup', 'config_from', 'config_startup',
'config_logs', 'config_files', 'config_stop',
]));
Alert::success('Successfully created new service option.')->flash();
return redirect()->route('admin.services.option.view', $option->id);
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.new')->withErrors(json_decode($ex->getMessage()))->withInput();
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception occurred while attempting to create this service. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option.new')->withInput();
return redirect()->route('admin.services.option.view', $option->id);
}
/**
* Handles POST request to create a new option variable.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @param \Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest $request
* @param \Pterodactyl\Models\ServiceOption $option
* @return \Illuminate\Http\RedirectResponse
*/
public function createVariable(Request $request, $id)
public function createVariable(OptionVariableFormRequest $request, ServiceOption $option)
{
$repo = new VariableRepository;
$this->variableCreationService->handle($option->id, $request->normalize());
$this->alert->success(trans('admin/services.variables.notices.variable_created'))->flash();
try {
$variable = $repo->create($id, $request->intersect([
'name', 'description', 'env_variable',
'default_value', 'options', 'rules',
]));
Alert::success('New variable successfully assigned to this service option.')->flash();
} catch (DisplayValidationException $ex) {
return redirect()->route('admin.services.option.variables', $id)->withErrors(json_decode($ex->getMessage()));
} catch (DisplayException $ex) {
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash();
}
return redirect()->route('admin.services.option.variables', $id);
return redirect()->route('admin.services.option.variables', $option->id);
}
/**
* Display option overview page.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function viewConfiguration(Request $request, $id)
@ -146,13 +147,14 @@ class OptionController extends Controller
/**
* Display variable overview page for a service option.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\View\View
*/
public function viewVariables(Request $request, $id)
{
return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')->findOrFail($id)]);
return view('admin.services.options.variables', ['option' => ServiceOption::with('variables')
->findOrFail($id), ]);
}
/**
@ -179,8 +181,8 @@ class OptionController extends Controller
/**
* Handles POST when editing a configration for a service option.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\RedirectResponse
*/
public function editConfiguration(Request $request, $id)
@ -207,7 +209,8 @@ class OptionController extends Controller
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')->flash();
Alert::danger('An unhandled exception occurred while attempting to perform that action. This error has been logged.')
->flash();
}
return redirect()->route('admin.services.option.view', $id);
@ -216,9 +219,9 @@ class OptionController extends Controller
/**
* Handles POST when editing a configration for a service option.
*
* @param \Pterodactyl\Http\Requests\Admin\Service\StoreOptionVariable $request
* @param int $option
* @param int $variable
* @param \Pterodactyl\Http\Requests\Admin\Service\StoreOptionVariable $request
* @param int $option
* @param int $variable
* @return \Illuminate\Http\RedirectResponse
*/
public function editVariable(StoreOptionVariable $request, $option, $variable)
@ -237,7 +240,8 @@ class OptionController extends Controller
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash();
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')
->flash();
}
return redirect()->route('admin.services.option.variables', $option);
@ -259,7 +263,8 @@ class OptionController extends Controller
Alert::danger($ex->getMessage())->flash();
} catch (\Exception $ex) {
Log::error($ex);
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')->flash();
Alert::danger('An unhandled exception was encountered while attempting to process that request. This error has been logged.')
->flash();
}
return redirect()->route('admin.services.option.scripts', $id);

View File

@ -0,0 +1,57 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Requests\Admin;
use Pterodactyl\Models\ServiceVariable;
class OptionVariableFormRequest extends AdminFormRequest
{
/**
* @return array
*/
public function rules()
{
return [
'name' => 'required|string|min:1|max:255',
'description' => 'sometimes|nullable|string',
'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . ServiceVariable::RESERVED_ENV_NAMES,
'default_value' => 'string',
'options' => 'sometimes|required|array',
'rules' => 'bail|required|string',
];
}
/**
* Run validation after the rules above have been applied.
*
* @param \Illuminate\Validation\Validator $validator
*/
public function withValidator($validator)
{
$validator->sometimes('default_value', $this->input('rules') ?? null, function ($input) {
return $input->default_value;
});
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Http\Requests\Admin;
use Pterodactyl\Models\ServiceOption;
class ServiceOptionFormRequest extends AdminFormRequest
{
/**
* @return array
*/
public function rules()
{
if ($this->method() === 'PATCH') {
return ServiceOption::getUpdateRulesForId($this->route()->parameter('option')->id);
}
return ServiceOption::getCreateRules();
}
}

View File

@ -24,10 +24,15 @@
namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class ServiceOption extends Model
class ServiceOption extends Model implements ValidableContract
{
use Eloquence, Validable;
/**
* The table associated with the model.
*
@ -42,15 +47,61 @@ class ServiceOption extends Model
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'service_id' => 'integer',
'script_is_privileged' => 'boolean',
];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'service_id' => 'integer',
'script_is_privileged' => 'boolean',
];
/**
* @var array
*/
protected static $applicationRules = [
'service_id' => 'required',
'name' => 'required',
'description' => 'required',
'tag' => 'required',
'docker_image' => 'sometimes',
'startup' => 'sometimes',
'config_from' => 'sometimes',
'config_stop' => 'required_without:config_from',
'config_startup' => 'required_without:config_from',
'config_logs' => 'required_without:config_from',
'config_files' => 'required_without:config_from',
];
/**
* @var array
*/
protected static $dataIntegrityRules = [
'service_id' => 'numeric|exists:services,id',
'name' => 'string|max:255',
'description' => 'string',
'tag' => 'alpha_num|max:60|unique:service_options,tag',
'docker_image' => 'string|max:255',
'startup' => 'nullable|string',
'config_from' => 'nullable|numeric|exists:service_options,id',
'config_stop' => 'nullable|string|max:255',
'config_startup' => 'nullable|json',
'config_logs' => 'nullable|json',
'config_files' => 'nullable|json',
];
/**
* @var array
*/
protected $attributes = [
'config_stop' => null,
'config_startup' => null,
'config_logs' => null,
'config_files' => null,
'startup' => null,
'docker_image' => null,
];
/**
* Returns the display startup string for the option and will use the parent
@ -136,6 +187,11 @@ class ServiceOption extends Model
return $this->hasMany(Pack::class, 'option_id');
}
/**
* Get the parent service option from which to copy scripts.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function copyFrom()
{
return $this->belongsTo(self::class, 'copy_script_from');

View File

@ -24,10 +24,22 @@
namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class ServiceVariable extends Model
class ServiceVariable extends Model implements ValidableContract
{
use Eloquence, Validable;
/**
* Reserved environment variable names.
*
* @var array
*/
const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID';
/**
* The table associated with the model.
*
@ -54,28 +66,35 @@ class ServiceVariable extends Model
];
/**
* Reserved environment variable names.
*
* @var array
*/
protected static $reservedNames = [
'SERVER_MEMORY',
'SERVER_IP',
'SERVER_PORT',
'ENV',
'HOME',
'USER',
protected static $applicationRules = [
'name' => 'required',
'env_variable' => 'required',
'rules' => 'required',
];
/**
* Returns an array of environment variable names that cannot be used.
*
* @return array
* @var array
*/
public static function reservedNames()
{
return self::$reservedNames;
}
protected static $dataIntegrityRules = [
'option_id' => 'exists:service_options,id',
'name' => 'string|between:1,255',
'description' => 'nullable|string',
'env_variable' => 'regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES,
'default_value' => 'string',
'user_viewable' => 'boolean',
'user_editable' => 'boolean',
'rules' => 'string',
];
/**
* @var array
*/
protected $attributes = [
'user_editable' => 0,
'user_viewable' => 0,
];
/**
* Returns the display executable for the option and will use the parent

View File

@ -38,6 +38,7 @@ use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
use Pterodactyl\Repositories\Eloquent\ApiPermissionRepository;
use Pterodactyl\Repositories\Eloquent\ServiceOptionRepository;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\OptionVariableRepository;
@ -48,6 +49,7 @@ use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
use Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface;
@ -61,6 +63,7 @@ class RepositoryServiceProvider extends ServiceProvider
*/
public function register()
{
// Eloquent Repositories
$this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class);
$this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class);
$this->app->bind(ApiPermissionRepositoryInterface::class, ApiPermissionRepository::class);
@ -72,6 +75,7 @@ class RepositoryServiceProvider extends ServiceProvider
$this->app->bind(ServerRepositoryInterface::class, ServerRepository::class);
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);
$this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class);
$this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class);
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
// Daemon Repositories

View File

@ -39,6 +39,14 @@ class DatabaseHostRepository extends EloquentRepository implements DatabaseHostR
return DatabaseHost::class;
}
/**
* {@inheritdoc}
*/
public function getWithViewDetails()
{
return $this->getBuilder()->withCount('databases')->with('node')->get();
}
/**
* {@inheritdoc}
*/

View File

@ -66,11 +66,19 @@ class LocationRepository extends SearchableRepository implements LocationReposit
/**
* {@inheritdoc}
*/
public function allWithDetails()
public function getAllWithDetails()
{
return $this->getBuilder()->withCount('nodes', 'servers')->get($this->getColumns());
}
/**
* {@inheritdoc}
*/
public function getAllWithNodes()
{
return $this->getBuilder()->with('nodes')->get($this->getColumns());
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,39 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Models\ServiceOption;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
class ServiceOptionRepository extends EloquentRepository implements ServiceOptionRepositoryInterface
{
/**
* {@inheritdoc}
*/
public function model()
{
return ServiceOption::class;
}
}

View File

@ -1,291 +0,0 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Repositories;
use DB;
use Validator;
use IPTools\Network;
use Pterodactyl\Models;
use Pterodactyl\Services\UuidService;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Exceptions\DisplayValidationException;
class NodeRepository
{
/**
* Creates a new node on the system.
*
* @param array $data
* @return \Pterodactyl\Models\Node
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function create(array $data)
{
// Validate Fields
$validator = Validator::make($data, [
'name' => 'required|regex:/^([\w .-]{1,100})$/',
'location_id' => 'required|numeric|min:1|exists:locations,id',
'public' => 'required|numeric|between:0,1',
'fqdn' => 'required|string|unique:nodes,fqdn',
'scheme' => 'required|regex:/^(http(s)?)$/',
'behind_proxy' => 'required|boolean',
'memory' => 'required|numeric|min:1',
'memory_overallocate' => 'required|numeric|min:-1',
'disk' => 'required|numeric|min:1',
'disk_overallocate' => 'required|numeric|min:-1',
'daemonBase' => 'required|regex:/^([\/][\d\w.\-\/]+)$/',
'daemonSFTP' => 'required|numeric|between:1,65535',
'daemonListen' => 'required|numeric|between:1,65535',
]);
// Run validator, throw catchable and displayable exception if it fails.
// Exception includes a JSON result of failed validation rules.
if ($validator->fails()) {
throw new DisplayValidationException(json_encode($validator->errors()));
}
// Verify the FQDN if using SSL
if (filter_var($data['fqdn'], FILTER_VALIDATE_IP) && $data['scheme'] === 'https') {
throw new DisplayException('A fully qualified domain name is required to use a secure comunication method on this node.');
}
// Verify FQDN is resolvable, or if not using SSL that the IP is valid.
if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) {
throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.');
}
// Should we be nulling the overallocations?
$data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate'];
$data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate'];
// Set the Secret
$uuid = new UuidService;
$data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret');
return Models\Node::create($data);
}
/**
* Updates a node on the system.
*
* @param int $id
* @param array $data
* @return \Pterodactyl\Models\Node
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function update($id, array $data)
{
$node = Models\Node::findOrFail($id);
// Validate Fields
$validator = $validator = Validator::make($data, [
'name' => 'regex:/^([\w .-]{1,100})$/',
'location_id' => 'numeric|min:1|exists:locations,id',
'public' => 'numeric|between:0,1',
'fqdn' => 'string|unique:nodes,fqdn,' . $id,
'scheme' => 'regex:/^(http(s)?)$/',
'behind_proxy' => 'boolean',
'memory' => 'numeric|min:1',
'memory_overallocate' => 'numeric|min:-1',
'disk' => 'numeric|min:1',
'disk_overallocate' => 'numeric|min:-1',
'upload_size' => 'numeric|min:0',
'daemonBase' => 'sometimes|regex:/^([\/][\d\w.\-\/]+)$/',
'daemonSFTP' => 'numeric|between:1,65535',
'daemonListen' => 'numeric|between:1,65535',
'reset_secret' => 'sometimes|nullable|accepted',
]);
// Run validator, throw catchable and displayable exception if it fails.
// Exception includes a JSON result of failed validation rules.
if ($validator->fails()) {
throw new DisplayValidationException(json_encode($validator->errors()));
}
// Verify the FQDN
if (isset($data['fqdn'])) {
// Verify the FQDN if using SSL
if ((isset($data['scheme']) && $data['scheme'] === 'https') || (! isset($data['scheme']) && $node->scheme === 'https')) {
if (filter_var($data['fqdn'], FILTER_VALIDATE_IP)) {
throw new DisplayException('A fully qualified domain name is required to use secure comunication on this node.');
}
}
// Verify FQDN is resolvable, or if not using SSL that the IP is valid.
if (! filter_var(gethostbyname($data['fqdn']), FILTER_VALIDATE_IP)) {
throw new DisplayException('The FQDN (or IP Address) provided does not resolve to a valid IP address.');
}
}
// Should we be nulling the overallocations?
if (isset($data['memory_overallocate'])) {
$data['memory_overallocate'] = ($data['memory_overallocate'] < 0) ? null : $data['memory_overallocate'];
}
if (isset($data['disk_overallocate'])) {
$data['disk_overallocate'] = ($data['disk_overallocate'] < 0) ? null : $data['disk_overallocate'];
}
// Set the Secret
if (isset($data['reset_secret']) && ! is_null($data['reset_secret'])) {
$uuid = new UuidService;
$data['daemonSecret'] = (string) $uuid->generate('nodes', 'daemonSecret');
unset($data['reset_secret']);
}
$oldDaemonKey = $node->daemonSecret;
$node->update($data);
try {
$node->guzzleClient(['X-Access-Token' => $oldDaemonKey])->request('PATCH', '/config', [
'json' => [
'web' => [
'listen' => $node->daemonListen,
'ssl' => [
'enabled' => (! $node->behind_proxy && $node->scheme === 'https'),
],
],
'sftp' => [
'path' => $node->daemonBase,
'port' => $node->daemonSFTP,
],
'remote' => [
'base' => config('app.url'),
],
'uploads' => [
'size_limit' => $node->upload_size,
],
'keys' => [
$node->daemonSecret,
],
],
]);
} catch (\Exception $ex) {
throw new DisplayException('Failed to update the node configuration, however your changes have been saved to the database. You will need to manually update the configuration file for the node to apply these changes.');
}
}
/**
* Adds allocations to a provided node.
*
* @param int $id
* @param array $data
* @return void
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\DisplayValidationException
*/
public function addAllocations($id, array $data)
{
$node = Models\Node::findOrFail($id);
$validator = Validator::make($data, [
'allocation_ip' => 'required|string',
'allocation_alias' => 'sometimes|required|string|max:255',
'allocation_ports' => 'required|array',
]);
if ($validator->fails()) {
throw new DisplayValidationException(json_encode($validator->errors()));
}
$explode = explode('/', $data['allocation_ip']);
if (count($explode) !== 1) {
if (! ctype_digit($explode[1]) || ($explode[1] > 32 || $explode[1] < 25)) {
throw new DisplayException('CIDR notation only allows masks between /32 and /25.');
}
}
DB::transaction(function () use ($data, $node) {
foreach (Network::parse(gethostbyname($data['allocation_ip'])) as $ip) {
foreach ($data['allocation_ports'] as $port) {
// Determine if this is a valid single port, or a valid port range.
if (! ctype_digit($port) && ! preg_match('/^(\d{1,5})-(\d{1,5})$/', $port)) {
throw new DisplayException('The mapping for <code>' . $port . '</code> is invalid and cannot be processed.');
}
if (preg_match('/^(\d{1,5})-(\d{1,5})$/', $port, $matches)) {
$block = range($matches[1], $matches[2]);
if (count($block) > 1000) {
throw new DisplayException('Adding more than 1000 ports at once is not supported. Please use a smaller port range.');
}
foreach ($block as $unit) {
// Insert into Database
Models\Allocation::firstOrCreate([
'node_id' => $node->id,
'ip' => $ip,
'port' => $unit,
'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null,
'server_id' => null,
]);
}
} else {
// Insert into Database
Models\Allocation::firstOrCreate([
'node_id' => $node->id,
'ip' => $ip,
'port' => $port,
'ip_alias' => isset($data['allocation_alias']) ? $data['allocation_alias'] : null,
'server_id' => null,
]);
}
}
}
});
}
/**
* Deletes a node on the system.
*
* @param int $id
* @return void
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function delete($id)
{
$node = Models\Node::withCount('servers')->findOrFail($id);
if ($node->servers_count > 0) {
throw new DisplayException('You cannot delete a node with servers currently attached to it.');
}
DB::transaction(function () use ($node) {
// Unlink Database Servers
Models\DatabaseHost::where('node_id', $node->id)->update(['node_id' => null]);
// Delete Allocations
Models\Allocation::where('node_id', $node->id)->delete();
// Delete Node
$node->delete();
});
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Services\Services\Options;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
class CreationService
{
/**
* @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
*/
protected $repository;
/**
* CreationService constructor.
*
* @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository
*/
public function __construct(ServiceOptionRepositoryInterface $repository)
{
$this->repository = $repository;
}
/**
* Create a new service option and assign it to the given service.
*
* @param array $data
* @return \Pterodactyl\Models\ServiceOption
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function handle(array $data)
{
if (! is_null(array_get($data, 'config_from'))) {
$results = $this->repository->findCountWhere([
['service_id', '=', array_get($data, 'service_id')],
['id', '=', array_get($data, 'config_from')],
]);
if ($results !== 1) {
throw new DisplayException(trans('admin/exceptions.service.options.must_be_child'));
}
} else {
$data['config_from'] = null;
}
return $this->repository->create($data);
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Services\Services\Variables;
use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface;
class VariableCreationService
{
/**
* @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface
*/
protected $serviceOptionRepository;
public function __construct(ServiceOptionRepositoryInterface $serviceOptionRepository)
{
$this->serviceOptionRepository = $serviceOptionRepository;
}
/**
* Create a new variable for a given service option.
*
* @param int $optionId
* @param array $data
* @return \Pterodactyl\Models\ServiceVariable
*/
public function handle($optionId, array $data)
{
$option = $this->serviceOptionRepository->find($optionId);
}
}

View File

@ -33,4 +33,9 @@ return [
'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.',
'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.',
],
'service' => [
'options' => [
'must_be_child' => 'The "Configuration From" directive for this option must be a child option for the selected service.',
],
],
];

View File

@ -0,0 +1,36 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
return [
'options' => [
'notices' => [
'option_created' => 'New service option was created successfully. You will need to restart any running daemons to apply this new service.',
],
],
'variables' => [
'notices' => [
'variable_created' => 'New variable has successfully been created and assigned to this service option.',
],
],
];

View File

@ -146,7 +146,7 @@
$('#pConfigFrom').select2();
});
$('#pServiceId').on('change', function (event) {
$('#pConfigFrom').html('<option value="0">None</option>').select2({
$('#pConfigFrom').html('<option value="">None</option>').select2({
data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) {
return {
id: item.id,

View File

@ -176,11 +176,12 @@ Route::group(['prefix' => 'services'], function () {
Route::post('/new', 'ServiceController@store');
Route::post('/view/{option}', 'ServiceController@edit');
Route::post('/option/new', 'OptionController@store');
Route::post('/option/{option}', 'OptionController@editConfiguration');
Route::post('/option/{option}/scripts', 'OptionController@updateScripts');
Route::post('/option/{option}/variables', 'OptionController@createVariable');
Route::post('/option/{option}/variables/{variable}', 'OptionController@editVariable')->name('admin.services.option.variables.edit');
Route::patch('/option/{option}', 'OptionController@editConfiguration');
Route::delete('/view/{id}', 'ServiceController@delete');
});