Remove all references of packs from the Panel

This commit is contained in:
Dane Everitt 2020-09-13 11:13:37 -07:00
parent f1978683cc
commit 3c7ffaaadb
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
60 changed files with 129 additions and 2517 deletions

View file

@ -14,7 +14,7 @@ use Pterodactyl\Models\Nest;
interface NestRepositoryInterface extends RepositoryInterface
{
/**
* Return a nest or all nests with their associated eggs, variables, and packs.
* Return a nest or all nests with their associated eggs and variables.
*
* @param int $id
* @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest
@ -24,7 +24,7 @@ interface NestRepositoryInterface extends RepositoryInterface
public function getWithEggs(int $id = null);
/**
* Return a nest or all nests and the count of eggs, packs, and servers for that nest.
* Return a nest or all nests and the count of eggs and servers for that nest.
*
* @param int|null $id
* @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection

View file

@ -1,25 +0,0 @@
<?php
namespace Pterodactyl\Contracts\Repository;
use Pterodactyl\Models\Pack;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
interface PackRepositoryInterface extends RepositoryInterface
{
/**
* Return a pack with the associated server models attached to it.
*
* @param \Pterodactyl\Models\Pack $pack
* @param bool $refresh
* @return \Pterodactyl\Models\Pack
*/
public function loadServerData(Pack $pack, bool $refresh = false): Pack;
/**
* Return a paginated listing of packs with their associated egg and server count.
*
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function paginateWithEggAndServerCount(): LengthAwarePaginator;
}

View file

@ -85,7 +85,7 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
/**
* Get data for use when updating a server on the Daemon. Returns an array of
* the egg and pack UUID which are used for build and rebuild. Only loads relations
* the egg which is used for build and rebuild. Only loads relations
* if they are missing, or refresh is set to true.
*
* @param \Pterodactyl\Models\Server $server

View file

@ -1,16 +0,0 @@
<?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\Exceptions\Service\Pack;
use Pterodactyl\Exceptions\DisplayException;
class InvalidFileMimeTypeException extends DisplayException
{
}

View file

@ -1,16 +0,0 @@
<?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\Exceptions\Service\Pack;
use Pterodactyl\Exceptions\DisplayException;
class InvalidPackArchiveFormatException extends DisplayException
{
}

View file

@ -1,16 +0,0 @@
<?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\Exceptions\Service\Pack;
use Pterodactyl\Exceptions\DisplayException;
class UnreadableZipArchiveException extends DisplayException
{
}

View file

@ -1,16 +0,0 @@
<?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\Exceptions\Service\Pack;
use Exception;
class ZipArchiveCreationException extends Exception
{
}

View file

@ -1,16 +0,0 @@
<?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\Exceptions\Service\Pack;
use Pterodactyl\Exceptions\DisplayException;
class ZipExtractionException extends DisplayException
{
}

View file

@ -1,251 +0,0 @@
<?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\Controllers\Admin;
use Illuminate\Http\Request;
use Pterodactyl\Models\Pack;
use Prologue\Alerts\AlertsMessageBag;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Packs\ExportPackService;
use Pterodactyl\Services\Packs\PackUpdateService;
use Pterodactyl\Services\Packs\PackCreationService;
use Pterodactyl\Services\Packs\PackDeletionService;
use Pterodactyl\Http\Requests\Admin\PackFormRequest;
use Pterodactyl\Services\Packs\TemplateUploadService;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
class PackController extends Controller
{
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
protected $alert;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* @var \Pterodactyl\Services\Packs\PackCreationService
*/
protected $creationService;
/**
* @var \Pterodactyl\Services\Packs\PackDeletionService
*/
protected $deletionService;
/**
* @var \Pterodactyl\Services\Packs\ExportPackService
*/
protected $exportService;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Packs\PackUpdateService
*/
protected $updateService;
/**
* @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface
*/
protected $serviceRepository;
/**
* @var \Pterodactyl\Services\Packs\TemplateUploadService
*/
protected $templateUploadService;
/**
* PackController constructor.
*
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Illuminate\Contracts\Config\Repository $config
* @param \Pterodactyl\Services\Packs\ExportPackService $exportService
* @param \Pterodactyl\Services\Packs\PackCreationService $creationService
* @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService
* @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository
* @param \Pterodactyl\Services\Packs\PackUpdateService $updateService
* @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $serviceRepository
* @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService
*/
public function __construct(
AlertsMessageBag $alert,
ConfigRepository $config,
ExportPackService $exportService,
PackCreationService $creationService,
PackDeletionService $deletionService,
PackRepositoryInterface $repository,
PackUpdateService $updateService,
NestRepositoryInterface $serviceRepository,
TemplateUploadService $templateUploadService
) {
$this->alert = $alert;
$this->config = $config;
$this->creationService = $creationService;
$this->deletionService = $deletionService;
$this->exportService = $exportService;
$this->repository = $repository;
$this->updateService = $updateService;
$this->serviceRepository = $serviceRepository;
$this->templateUploadService = $templateUploadService;
}
/**
* Display listing of all packs on the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('admin.packs.index', [
'packs' => $this->repository->paginateWithEggAndServerCount(),
]);
}
/**
* Display new pack creation form.
*
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function create()
{
return view('admin.packs.new', [
'nests' => $this->serviceRepository->getWithEggs(),
]);
}
/**
* Display new pack creation modal for use with template upload.
*
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function newTemplate()
{
return view('admin.packs.modal', [
'nests' => $this->serviceRepository->getWithEggs(),
]);
}
/**
* Handle create pack request and route user to location.
*
* @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request
* @return \Illuminate\View\View
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException
* @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException
*/
public function store(PackFormRequest $request)
{
if ($request->filled('from_template')) {
$pack = $this->templateUploadService->handle($request->input('egg_id'), $request->file('file_upload'));
} else {
$pack = $this->creationService->handle($request->normalize(), $request->file('file_upload'));
}
$this->alert->success(trans('admin/pack.notices.pack_created'))->flash();
return redirect()->route('admin.packs.view', $pack->id);
}
/**
* Display pack view template to user.
*
* @param \Pterodactyl\Models\Pack $pack
* @return \Illuminate\View\View
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function view(Pack $pack)
{
return view('admin.packs.view', [
'pack' => $this->repository->loadServerData($pack),
'nests' => $this->serviceRepository->getWithEggs(),
]);
}
/**
* Handle updating or deleting pack information.
*
* @param \Pterodactyl\Http\Requests\Admin\PackFormRequest $request
* @param \Pterodactyl\Models\Pack $pack
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/
public function update(PackFormRequest $request, Pack $pack)
{
$this->updateService->handle($pack, $request->normalize());
$this->alert->success(trans('admin/pack.notices.pack_updated'))->flash();
return redirect()->route('admin.packs.view', $pack->id);
}
/**
* Delete a pack if no servers are attached to it currently.
*
* @param \Pterodactyl\Models\Pack $pack
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/
public function destroy(Pack $pack)
{
$this->deletionService->handle($pack->id);
$this->alert->success(trans('admin/pack.notices.pack_deleted', [
'name' => $pack->name,
]))->flash();
return redirect()->route('admin.packs');
}
/**
* Creates an archive of the pack and downloads it to the browser.
*
* @param \Pterodactyl\Models\Pack $pack
* @param bool|string $files
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException
*/
public function export(Pack $pack, $files = false)
{
$filename = $this->exportService->handle($pack, is_string($files));
if (is_string($files)) {
return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true);
}
return response()->download($filename, 'pack-' . $pack->name . '.json', [
'Content-Type' => 'application/json',
])->deleteFileAfterSend(true);
}
}

View file

@ -240,7 +240,7 @@ class ServersController extends Controller
}
/**
* Reinstalls the server with the currently assigned pack and service.
* Reinstalls the server with the currently assigned service.
*
* @param \Pterodactyl\Models\Server $server
* @return \Illuminate\Http\RedirectResponse

View file

@ -1,49 +0,0 @@
<?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;
use Pterodactyl\Models\Pack;
use Pterodactyl\Services\Packs\PackCreationService;
class PackFormRequest extends AdminFormRequest
{
/**
* @return array
*/
public function rules()
{
if ($this->method() === 'PATCH') {
return Pack::getRulesForUpdate($this->route()->parameter('pack')->id);
}
return Pack::getRules();
}
/**
* Run validation after the rules above have been applied.
*
* @param \Illuminate\Validation\Validator $validator
*/
public function withValidator($validator)
{
if ($this->method() !== 'POST') {
return;
}
$validator->after(function ($validator) {
$mimetypes = implode(',', PackCreationService::VALID_UPLOAD_TYPES);
/* @var $validator \Illuminate\Validation\Validator */
$validator->sometimes('file_upload', 'sometimes|required|file|mimetypes:' . $mimetypes, function () {
return true;
});
});
}
}

View file

@ -62,15 +62,6 @@ class ServerFormRequest extends AdminFormRequest
], function ($input) {
return ! ($input->auto_deploy);
});
$validator->sometimes('pack_id', [
Rule::exists('packs', 'id')->where(function ($query) {
$query->where('selectable', 1);
$query->where('egg_id', $this->input('egg_id'));
}),
], function ($input) {
return $input->pack_id !== 0 && $input->pack_id !== null;
});
});
}
}

View file

@ -36,7 +36,6 @@ class StoreServerRequest extends ApplicationApiRequest
'description' => array_merge(['nullable'], $rules['description']),
'user' => $rules['owner_id'],
'egg' => $rules['egg_id'],
'pack' => $rules['pack_id'],
'docker_image' => $rules['image'],
'startup' => $rules['startup'],
'environment' => 'present|array',
@ -88,7 +87,6 @@ class StoreServerRequest extends ApplicationApiRequest
'description' => array_get($data, 'description'),
'owner_id' => array_get($data, 'user'),
'egg_id' => array_get($data, 'egg'),
'pack_id' => array_get($data, 'pack'),
'image' => array_get($data, 'docker_image'),
'startup' => array_get($data, 'startup'),
'environment' => array_get($data, 'environment'),

View file

@ -31,7 +31,6 @@ class UpdateServerStartupRequest extends ApplicationApiRequest
'startup' => $data['startup'],
'environment' => 'present|array',
'egg' => $data['egg_id'],
'pack' => $data['pack_id'],
'image' => $data['image'],
'skip_scripts' => 'present|boolean',
];
@ -48,7 +47,6 @@ class UpdateServerStartupRequest extends ApplicationApiRequest
return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([
'egg_id' => array_get($data, 'egg'),
'pack_id' => array_get($data, 'pack'),
'docker_image' => array_get($data, 'image'),
])->toArray();
}

View file

@ -63,7 +63,6 @@ class ApiKey extends Model
'r_' . AdminAcl::RESOURCE_LOCATIONS => 'int',
'r_' . AdminAcl::RESOURCE_NESTS => 'int',
'r_' . AdminAcl::RESOURCE_NODES => 'int',
'r_' . AdminAcl::RESOURCE_PACKS => 'int',
'r_' . AdminAcl::RESOURCE_SERVERS => 'int',
];
@ -110,7 +109,6 @@ class ApiKey extends Model
'r_' . AdminAcl::RESOURCE_LOCATIONS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_NESTS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_NODES => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_PACKS => 'integer|min:0|max:3',
'r_' . AdminAcl::RESOURCE_SERVERS => 'integer|min:0|max:3',
];

View file

@ -35,7 +35,6 @@ namespace Pterodactyl\Models;
* @property \Pterodactyl\Models\Nest $nest
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Pack[] $packs
* @property \Pterodactyl\Models\Egg|null $scriptFrom
* @property \Pterodactyl\Models\Egg|null $configFrom
*/
@ -247,16 +246,6 @@ class Egg extends Model
return $this->hasMany(EggVariable::class, 'egg_id');
}
/**
* Gets all packs associated with this egg.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function packs()
{
return $this->hasMany(Pack::class, 'egg_id');
}
/**
* Get the parent egg from which to copy scripts.
*

View file

@ -13,7 +13,6 @@ namespace Pterodactyl\Models;
*
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Egg[] $eggs
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Pack[] $packs
*/
class Nest extends Model
{
@ -59,16 +58,6 @@ class Nest extends Model
return $this->hasMany(Egg::class);
}
/**
* Returns all of the packs associated with a nest, regardless of the egg.
*
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function packs()
{
return $this->hasManyThrough(Pack::class, Egg::class, 'nest_id', 'egg_id');
}
/**
* Gets all servers associated with this nest.
*

View file

@ -1,106 +0,0 @@
<?php
namespace Pterodactyl\Models;
use Pterodactyl\Models\Traits\Searchable;
/**
* @property int $id
* @property int $egg_id
* @property string $uuid
* @property string $name
* @property string $version
* @property string $description
* @property bool $selectable
* @property bool $visible
* @property bool $locked
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*
* @property \Pterodactyl\Models\Egg|null $egg
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
*/
class Pack extends Model
{
use Searchable;
/**
* The resource name for this model when it is transformed into an
* API representation using fractal.
*/
const RESOURCE_NAME = 'pack';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'packs';
/**
* Fields that are mass assignable.
*
* @var array
*/
protected $fillable = [
'egg_id', 'uuid', 'name', 'version', 'description', 'selectable', 'visible', 'locked',
];
/**
* @var array
*/
public static $validationRules = [
'name' => 'required|string',
'version' => 'required|string',
'description' => 'sometimes|nullable|string',
'selectable' => 'sometimes|required|boolean',
'visible' => 'sometimes|required|boolean',
'locked' => 'sometimes|required|boolean',
'egg_id' => 'required|exists:eggs,id',
];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'egg_id' => 'integer',
'selectable' => 'boolean',
'visible' => 'boolean',
'locked' => 'boolean',
];
/**
* Parameters for search querying.
*
* @var array
*/
protected $searchableColumns = [
'name' => 10,
'uuid' => 8,
'egg.name' => 6,
'egg.docker_image' => 5,
'version' => 2,
];
/**
* Gets egg associated with a service pack.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function egg()
{
return $this->belongsTo(Egg::class);
}
/**
* Gets servers associated with a pack.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servers()
{
return $this->hasMany(Server::class);
}
}

View file

@ -28,7 +28,6 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property int $allocation_id
* @property int $nest_id
* @property int $egg_id
* @property int|null $pack_id
* @property string $startup
* @property string $image
* @property int $installed
@ -42,7 +41,6 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers
* @property \Pterodactyl\Models\Allocation $allocation
* @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
* @property \Pterodactyl\Models\Pack|null $pack
* @property \Pterodactyl\Models\Node $node
* @property \Pterodactyl\Models\Nest $nest
* @property \Pterodactyl\Models\Egg $egg
@ -122,7 +120,6 @@ class Server extends Model
'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
'nest_id' => 'required|exists:nests,id',
'egg_id' => 'required|exists:eggs,id',
'pack_id' => 'sometimes|nullable|numeric|min:0',
'startup' => 'required|string',
'skip_scripts' => 'sometimes|boolean',
'image' => 'required|string|max:255',
@ -151,7 +148,6 @@ class Server extends Model
'allocation_id' => 'integer',
'nest_id' => 'integer',
'egg_id' => 'integer',
'pack_id' => 'integer',
'installed' => 'integer',
'database_limit' => 'integer',
'allocation_limit' => 'integer',
@ -171,7 +167,6 @@ class Server extends Model
'user.email' => 40,
'user.username' => 30,
'node.name' => 10,
'pack.name' => 10,
];
/**
@ -234,16 +229,6 @@ class Server extends Model
return $this->hasMany(Allocation::class, 'server_id');
}
/**
* Gets information for the pack associated with this server.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function pack()
{
return $this->belongsTo(Pack::class);
}
/**
* Gets information for the nest associated with this server.
*

View file

@ -6,7 +6,6 @@ use Illuminate\Support\ServiceProvider;
use Pterodactyl\Repositories\Eloquent\EggRepository;
use Pterodactyl\Repositories\Eloquent\NestRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\PackRepository;
use Pterodactyl\Repositories\Eloquent\TaskRepository;
use Pterodactyl\Repositories\Eloquent\UserRepository;
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
@ -23,7 +22,6 @@ use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\EggVariableRepository;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
@ -60,7 +58,6 @@ class RepositoryServiceProvider extends ServiceProvider
$this->app->bind(LocationRepositoryInterface::class, LocationRepository::class);
$this->app->bind(NestRepositoryInterface::class, NestRepository::class);
$this->app->bind(NodeRepositoryInterface::class, NodeRepository::class);
$this->app->bind(PackRepositoryInterface::class, PackRepository::class);
$this->app->bind(ScheduleRepositoryInterface::class, ScheduleRepository::class);
$this->app->bind(ServerRepositoryInterface::class, ServerRepository::class);
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);

View file

@ -26,7 +26,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa
}
/**
* Return a nest or all nests with their associated eggs, variables, and packs.
* Return a nest or all nests with their associated eggs and variables.
*
* @param int $id
* @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest
@ -35,7 +35,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa
*/
public function getWithEggs(int $id = null)
{
$instance = $this->getBuilder()->with('eggs.packs', 'eggs.variables');
$instance = $this->getBuilder()->with('eggs', 'eggs.variables');
if (! is_null($id)) {
$instance = $instance->find($id, $this->getColumns());
@ -50,7 +50,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa
}
/**
* Return a nest or all nests and the count of eggs, packs, and servers for that nest.
* Return a nest or all nests and the count of eggs and servers for that nest.
*
* @param int|null $id
* @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection
@ -59,7 +59,7 @@ class NestRepository extends EloquentRepository implements NestRepositoryInterfa
*/
public function getWithCounts(int $id = null)
{
$instance = $this->getBuilder()->withCount(['eggs', 'packs', 'servers']);
$instance = $this->getBuilder()->withCount(['eggs', 'servers']);
if (! is_null($id)) {
$instance = $instance->find($id, $this->getColumns());

View file

@ -1,52 +0,0 @@
<?php
namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Models\Pack;
use Pterodactyl\Repositories\Concerns\Searchable;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
class PackRepository extends EloquentRepository implements PackRepositoryInterface
{
use Searchable;
/**
* Return the model backing this repository.
*
* @return string
*/
public function model()
{
return Pack::class;
}
/**
* Return a pack with the associated server models attached to it.
*
* @param \Pterodactyl\Models\Pack $pack
* @param bool $refresh
* @return \Pterodactyl\Models\Pack
*/
public function loadServerData(Pack $pack, bool $refresh = false): Pack
{
if ($refresh) {
$pack->load(['servers.node', 'servers.user']);
}
$pack->loadMissing(['servers.node', 'servers.user']);
return $pack;
}
/**
* Return a paginated listing of packs with their associated egg and server count.
*
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public function paginateWithEggAndServerCount(): LengthAwarePaginator
{
return $this->getBuilder()->with('egg')->withCount('servers')
->paginate(50, $this->getColumns());
}
}

View file

@ -63,7 +63,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
*/
public function getDataForRebuild(int $server = null, int $node = null): Collection
{
$instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg', 'node']);
$instance = $this->getBuilder()->with(['allocation', 'allocations', 'egg', 'node']);
if (! is_null($server) && is_null($node)) {
$instance = $instance->where('id', '=', $server);
@ -83,7 +83,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
*/
public function getDataForReinstall(int $server = null, int $node = null): Collection
{
$instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg', 'node']);
$instance = $this->getBuilder()->with(['allocation', 'allocations', 'egg', 'node']);
if (! is_null($server) && is_null($node)) {
$instance = $instance->where('id', '=', $server);
@ -140,7 +140,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
*/
public function getDataForCreation(Server $server, bool $refresh = false): Server
{
foreach (['allocation', 'allocations', 'pack', 'egg'] as $relation) {
foreach (['allocation', 'allocations', 'egg'] as $relation) {
if (! $server->relationLoaded($relation) || $refresh) {
$server->load($relation);
}
@ -167,7 +167,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
/**
* Get data for use when updating a server on the Daemon. Returns an array of
* the egg and pack UUID which are used for build and rebuild. Only loads relations
* the egg which is used for build and rebuild. Only loads relations
* if they are missing, or refresh is set to true.
*
* @param \Pterodactyl\Models\Server $server
@ -180,13 +180,8 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
$server->load('egg');
}
if (! $server->relationLoaded('pack') || $refresh) {
$server->load('pack');
}
return [
'egg' => $server->getRelation('egg')->uuid,
'pack' => is_null($server->getRelation('pack')) ? null : $server->getRelation('pack')->uuid,
];
}

View file

@ -34,7 +34,6 @@ class AdminAcl
const RESOURCE_EGGS = 'eggs';
const RESOURCE_DATABASE_HOSTS = 'database_hosts';
const RESOURCE_SERVER_DATABASES = 'server_databases';
const RESOURCE_PACKS = 'packs';
/**
* Determine if an API key has permission to perform a specific read/write operation.

View file

@ -1,97 +0,0 @@
<?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\Services\Packs;
use ZipArchive;
use Pterodactyl\Models\Pack;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException;
class ExportPackService
{
/**
* @var \ZipArchive
*/
protected $archive;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Illuminate\Contracts\Filesystem\Factory
*/
protected $storage;
/**
* ExportPackService constructor.
*
* @param \Illuminate\Contracts\Filesystem\Factory $storage
* @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository
* @param \ZipArchive $archive
*/
public function __construct(
FilesystemFactory $storage,
PackRepositoryInterface $repository,
ZipArchive $archive
) {
$this->archive = $archive;
$this->repository = $repository;
$this->storage = $storage;
}
/**
* Prepare a pack for export.
*
* @param int|\Pterodactyl\Models\Pack $pack
* @param bool $files
* @return string
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException
*/
public function handle($pack, $files = false)
{
if (! $pack instanceof Pack) {
$pack = $this->repository->find($pack);
}
$json = [
'name' => $pack->name,
'version' => $pack->version,
'description' => $pack->description,
'selectable' => $pack->selectable,
'visible' => $pack->visible,
'locked' => $pack->locked,
];
$filename = tempnam(sys_get_temp_dir(), 'pterodactyl_');
if ($files) {
if (! $this->archive->open($filename, $this->archive::CREATE)) {
throw new ZipArchiveCreationException;
}
foreach ($this->storage->disk()->files('packs/' . $pack->uuid) as $file) {
$this->archive->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file)));
}
$this->archive->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT));
$this->archive->close();
} else {
$fp = fopen($filename, 'a+');
fwrite($fp, json_encode($json, JSON_PRETTY_PRINT));
fclose($fp);
}
return $filename;
}
}

View file

@ -1,104 +0,0 @@
<?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\Services\Packs;
use Ramsey\Uuid\Uuid;
use Illuminate\Http\UploadedFile;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
class PackCreationService
{
const VALID_UPLOAD_TYPES = [
'application/gzip',
'application/x-gzip',
];
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Illuminate\Contracts\Filesystem\Factory
*/
protected $storage;
/**
* PackCreationService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Illuminate\Contracts\Filesystem\Factory $storage
* @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository
*/
public function __construct(
ConnectionInterface $connection,
FilesystemFactory $storage,
PackRepositoryInterface $repository
) {
$this->connection = $connection;
$this->repository = $repository;
$this->storage = $storage;
}
/**
* Add a new service pack to the system.
*
* @param array $data
* @param \Illuminate\Http\UploadedFile|null $file
* @return \Pterodactyl\Models\Pack
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
*/
public function handle(array $data, UploadedFile $file = null)
{
if (! is_null($file)) {
if (! $file->isValid()) {
throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload'));
}
if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) {
throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [
'type' => implode(', ', self::VALID_UPLOAD_TYPES),
]));
}
}
// Transform values to boolean
$data['selectable'] = isset($data['selectable']);
$data['visible'] = isset($data['visible']);
$data['locked'] = isset($data['locked']);
$this->connection->beginTransaction();
$pack = $this->repository->create(array_merge(
['uuid' => Uuid::uuid4()],
$data
));
$this->storage->disk()->makeDirectory('packs/' . $pack->uuid);
if (! is_null($file)) {
$file->storeAs('packs/' . $pack->uuid, 'archive.tar.gz');
}
$this->connection->commit();
return $pack;
}
}

View file

@ -1,85 +0,0 @@
<?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\Services\Packs;
use Pterodactyl\Models\Pack;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
class PackDeletionService
{
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
/**
* @var \Illuminate\Contracts\Filesystem\Factory
*/
protected $storage;
/**
* PackDeletionService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Illuminate\Contracts\Filesystem\Factory $storage
* @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository
*/
public function __construct(
ConnectionInterface $connection,
FilesystemFactory $storage,
PackRepositoryInterface $repository,
ServerRepositoryInterface $serverRepository
) {
$this->connection = $connection;
$this->repository = $repository;
$this->serverRepository = $serverRepository;
$this->storage = $storage;
}
/**
* Delete a pack from the database as well as the archive stored on the server.
*
* @param int|\Pterodactyl\Models\Pack $pack
*
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($pack)
{
if (! $pack instanceof Pack) {
$pack = $this->repository->setColumns(['id', 'uuid'])->find($pack);
}
$count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]);
if ($count !== 0) {
throw new HasActiveServersException(trans('exceptions.packs.delete_has_servers'));
}
$this->connection->beginTransaction();
$this->repository->delete($pack->id);
$this->storage->disk()->deleteDirectory('packs/' . $pack->uuid);
$this->connection->commit();
}
}

View file

@ -1,75 +0,0 @@
<?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\Services\Packs;
use Pterodactyl\Models\Pack;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class PackUpdateService
{
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
/**
* PackUpdateService constructor.
*
* @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository
*/
public function __construct(
PackRepositoryInterface $repository,
ServerRepositoryInterface $serverRepository
) {
$this->repository = $repository;
$this->serverRepository = $serverRepository;
}
/**
* Update a pack.
*
* @param int|\Pterodactyl\Models\Pack $pack
* @param array $data
* @return bool
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($pack, array $data)
{
if (! $pack instanceof Pack) {
$pack = $this->repository->setColumns(['id', 'egg_id'])->find($pack);
}
if ((int) array_get($data, 'egg_id', $pack->egg_id) !== $pack->egg_id) {
$count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]);
if ($count !== 0) {
throw new HasActiveServersException(trans('exceptions.packs.update_has_servers'));
}
}
// Transform values to boolean
$data['selectable'] = isset($data['selectable']);
$data['visible'] = isset($data['visible']);
$data['locked'] = isset($data['locked']);
return $this->repository->withoutFreshModel()->update($pack->id, $data);
}
}

View file

@ -1,125 +0,0 @@
<?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\Services\Packs;
use ZipArchive;
use Illuminate\Http\UploadedFile;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException;
use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException;
class TemplateUploadService
{
const VALID_UPLOAD_TYPES = [
'application/zip',
'text/plain',
'application/json',
];
/**
* @var \ZipArchive
*/
protected $archive;
/**
* @var \Pterodactyl\Services\Packs\PackCreationService
*/
protected $creationService;
/**
* TemplateUploadService constructor.
*
* @param \Pterodactyl\Services\Packs\PackCreationService $creationService
* @param \ZipArchive $archive
*/
public function __construct(
PackCreationService $creationService,
ZipArchive $archive
) {
$this->archive = $archive;
$this->creationService = $creationService;
}
/**
* Process an uploaded file to create a new pack from a JSON or ZIP format.
*
* @param int $egg
* @param \Illuminate\Http\UploadedFile $file
* @return \Pterodactyl\Models\Pack
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException
*/
public function handle($egg, UploadedFile $file)
{
if (! $file->isValid()) {
throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload'));
}
if (! in_array($file->getMimeType(), self::VALID_UPLOAD_TYPES)) {
throw new InvalidFileMimeTypeException(trans('exceptions.packs.invalid_mime', [
'type' => implode(', ', self::VALID_UPLOAD_TYPES),
]));
}
if ($file->getMimeType() === 'application/zip') {
return $this->handleArchive($egg, $file);
} else {
$json = json_decode($file->openFile()->fread($file->getSize()), true);
$json['egg_id'] = $egg;
return $this->creationService->handle($json);
}
}
/**
* Process a ZIP file to create a pack and stored archive.
*
* @param int $egg
* @param \Illuminate\Http\UploadedFile $file
* @return \Pterodactyl\Models\Pack
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException
* @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException
* @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException
*/
protected function handleArchive($egg, $file)
{
if (! $this->archive->open($file->getRealPath())) {
throw new UnreadableZipArchiveException(trans('exceptions.packs.unreadable'));
}
if (! $this->archive->locateName('import.json') || ! $this->archive->locateName('archive.tar.gz')) {
throw new InvalidPackArchiveFormatException(trans('exceptions.packs.invalid_archive_exception'));
}
$json = json_decode($this->archive->getFromName('import.json'), true);
$json['egg_id'] = $egg;
$pack = $this->creationService->handle($json);
if (! $this->archive->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) {
// @todo delete the pack that was created.
throw new ZipExtractionException(trans('exceptions.packs.zip_extraction'));
}
$this->archive->close();
return $pack;
}
}

View file

@ -15,7 +15,7 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class ServerConfigurationStructureService
{
const REQUIRED_RELATIONS = ['allocation', 'allocations', 'pack', 'egg'];
const REQUIRED_RELATIONS = ['allocation', 'allocations', 'egg'];
/**
* @var \Pterodactyl\Services\Servers\EnvironmentService
@ -139,7 +139,6 @@ class ServerConfigurationStructureService
],
'service' => [
'egg' => $server->egg->uuid,
'pack' => $server->pack ? $server->pack->uuid : null,
'skip_scripts' => $server->skip_scripts,
],
'rebuild' => false,

View file

@ -246,7 +246,6 @@ class ServerCreationService
'allocation_id' => Arr::get($data, 'allocation_id'),
'nest_id' => Arr::get($data, 'nest_id'),
'egg_id' => Arr::get($data, 'egg_id'),
'pack_id' => empty($data['pack_id']) ? null : $data['pack_id'],
'startup' => Arr::get($data, 'startup'),
'image' => Arr::get($data, 'image'),
'database_limit' => Arr::get($data, 'database_limit') ?? 0,

View file

@ -140,7 +140,6 @@ class StartupModificationService
'startup' => array_get($data, 'startup', $server->startup),
'nest_id' => array_get($data, 'nest_id', $server->nest_id),
'egg_id' => array_get($data, 'egg_id', $server->egg_id),
'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null,
'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
'image' => array_get($data, 'docker_image', $server->image),
]);

View file

@ -25,7 +25,7 @@ class LocationTransformer extends BaseTransformer
}
/**
* Return a generic transformed pack array.
* Return a generic transformed location array.
*
* @param \Pterodactyl\Models\Location $location
* @return array

View file

@ -1,40 +0,0 @@
<?php
namespace Pterodactyl\Transformers\Api\Application;
use Pterodactyl\Models\Pack;
class PackTransformer extends BaseTransformer
{
/**
* Return the resource name for the JSONAPI output.
*
* @return string
*/
public function getResourceName(): string
{
return Pack::RESOURCE_NAME;
}
/**
* Return a transformed User model that can be consumed by external services.
*
* @param \Pterodactyl\Models\Pack $pack
* @return array
*/
public function transform(Pack $pack): array
{
return [
'id' => $pack->id,
'uuid' => $pack->uuid,
'egg' => $pack->egg_id,
'name' => $pack->name,
'description' => $pack->description,
'is_selectable' => (bool) $pack->selectable,
'is_visible' => (bool) $pack->visible,
'is_locked' => (bool) $pack->locked,
'created_at' => $this->formatTimestamp($pack->created_at),
'updated_at' => $this->formatTimestamp($pack->updated_at),
];
}
}

View file

@ -22,7 +22,6 @@ class ServerTransformer extends BaseTransformer
'allocations',
'user',
'subusers',
'pack',
'nest',
'egg',
'variables',
@ -87,7 +86,6 @@ class ServerTransformer extends BaseTransformer
'allocation' => $server->allocation_id,
'nest' => $server->nest_id,
'egg' => $server->egg_id,
'pack' => $server->pack_id,
'container' => [
'startup_command' => $server->startup,
'image' => $server->image,
@ -156,28 +154,6 @@ class ServerTransformer extends BaseTransformer
return $this->item($server->getRelation('user'), $this->makeTransformer(UserTransformer::class), 'user');
}
/**
* Return a generic array with pack information for this server.
*
* @param \Pterodactyl\Models\Server $server
* @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource
*
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
*/
public function includePack(Server $server)
{
if (! $this->authorize(AdminAcl::RESOURCE_PACKS)) {
return $this->null();
}
$server->loadMissing('pack');
if (is_null($server->getRelation('pack'))) {
return $this->null();
}
return $this->item($server->getRelation('pack'), $this->makeTransformer(PackTransformer::class), 'pack');
}
/**
* Return a generic array with nest information for this server.
*
@ -236,7 +212,7 @@ class ServerTransformer extends BaseTransformer
}
/**
* Return a generic array with pack information for this server.
* Return a generic array with location information for this server.
*
* @param \Pterodactyl\Models\Server $server
* @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource
@ -255,7 +231,7 @@ class ServerTransformer extends BaseTransformer
}
/**
* Return a generic array with pack information for this server.
* Return a generic array with node information for this server.
*
* @param \Pterodactyl\Models\Server $server
* @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource

View file

@ -56,7 +56,6 @@ return [
'admin' => [
'servers' => env('APP_PAGINATE_ADMIN_SERVERS', 25),
'users' => env('APP_PAGINATE_ADMIN_USERS', 25),
'packs' => env('APP_PAGINATE_ADMIN_PACKS', 50),
],
'api' => [
'nodes' => env('APP_PAGINATE_API_NODES', 25),

View file

@ -32,7 +32,6 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) {
'io' => 500,
'cpu' => 0,
'oom_disabled' => 0,
'pack_id' => null,
'installed' => 1,
'database_limit' => null,
'allocation_limit' => null,
@ -132,18 +131,6 @@ $factory->state(Pterodactyl\Models\EggVariable::class, 'editable', function () {
return ['user_editable' => 1];
});
$factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) {
return [
'uuid' => $faker->uuid,
'name' => $faker->word,
'description' => null,
'version' => $faker->randomNumber(),
'selectable' => 1,
'visible' => 1,
'locked' => 0,
];
});
$factory->define(Pterodactyl\Models\Subuser::class, function (Faker $faker) {
return [];
});

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DropPacksFromServers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('servers', function (Blueprint $table) {
$table->dropForeign(['pack_id']);
$table->dropColumn('pack_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('servers', function (Blueprint $table) {
$table->unsignedInteger('pack_id')->after('egg_id')->nullable();
$table->foreign('pack_id')->references('id')->on('packs');
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DropPacksFromApiKeyPermissions extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('api_keys', function (Blueprint $table) {
$table->dropColumn('r_packs');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('api_keys', function (Blueprint $table) {
$table->unsignedTinyInteger('r_packs')->default(0);
});
}
}

View file

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DropPacksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('packs');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::create('packs', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('egg_id');
$table->char('uuid', 36)->unique();
$table->string('name');
$table->string('version');
$table->text('description')->nullable();
$table->tinyInteger('selectable')->default(1);
$table->tinyInteger('visible')->default(1);
$table->tinyInteger('locked')->default(0);
$table->timestamps();
});
Schema::table('packs', function (Blueprint $table) {
$table->foreign('egg_id')->references('id')->on('eggs')->cascadeOnDelete();
});
}
}

View file

@ -1,16 +0,0 @@
<?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
*/
return [
'notices' => [
'pack_updated' => 'Pack has been successfully updated.',
'pack_deleted' => 'Successfully deleted the pack ":name" from the system.',
'pack_created' => 'A new pack was successfully created on the system and is now available for deployment to servers.',
],
];

View file

@ -32,15 +32,6 @@ return [
'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.',
],
],
'packs' => [
'delete_has_servers' => 'Cannot delete a pack that is attached to active servers.',
'update_has_servers' => 'Cannot modify the associated option ID when servers are currently attached to a pack.',
'invalid_upload' => 'The file provided does not appear to be valid.',
'invalid_mime' => 'The file provided does not meet the required type :type',
'unreadable' => 'The archive provided could not be opened by the server.',
'zip_extraction' => 'An exception was encountered while attempting to extract the archive provided onto the server.',
'invalid_archive_exception' => 'The pack archive provided appears to be missing a required archive.tar.gz or import.json file in the base directory.',
],
'subusers' => [
'editing_self' => 'Editing your own subuser account is not permitted.',
'user_is_owner' => 'You cannot add the server owner as a subuser for this server.',

View file

@ -42,7 +42,6 @@
<th>Name</th>
<th>Description</th>
<th class="text-center">Eggs</th>
<th class="text-center">Packs</th>
<th class="text-center">Servers</th>
</tr>
@foreach($nests as $nest)
@ -51,7 +50,6 @@
<td class="middle"><a href="{{ route('admin.nests.view', $nest->id) }}" data-toggle="tooltip" data-placement="right" title="{{ $nest->author }}">{{ $nest->name }}</a></td>
<td class="col-xs-6 middle">{{ $nest->description }}</td>
<td class="text-center middle">{{ $nest->eggs_count }}</td>
<td class="text-center middle">{{ $nest->packs_count }}</td>
<td class="text-center middle">{{ $nest->servers_count }}</td>
</tr>
@endforeach

View file

@ -1,70 +0,0 @@
{{-- 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 --}}
@extends('layouts.admin')
@section('title')
List Packs
@endsection
@section('content-header')
<h1>Packs<small>All service packs available on the system.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li class="active">Packs</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack List</h3>
<div class="box-tools">
<form action="{{ route('admin.packs') }}" method="GET">
<div class="input-group input-group-sm">
<input type="text" name="query" class="form-control pull-right" style="width:30%;" value="{{ request()->input('query') }}" placeholder="Search Packs">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
<a href="{{ route('admin.packs.new') }}"><button type="button" class="btn btn-sm btn-primary" style="border-radius: 0 3px 3px 0;margin-left:-1px;">Create New</button></a>
</div>
</div>
</form>
</div>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tbody>
<tr>
<th>ID</th>
<th>Pack Name</th>
<th>Version</th>
<th>Description</th>
<th>Egg</th>
<th class="text-center">Servers</th>
</tr>
@foreach ($packs as $pack)
<tr>
<td class="middle"><code>{{ $pack->id }}</code></td>
<td class="middle"><a href="{{ route('admin.packs.view', $pack->id) }}" data-toggle="tooltip" data-placement="right" title="{{ $pack->uuid }}">{{ $pack->name }}</a></td>
<td class="middle"><code>{{ $pack->version }}</code></td>
<td class="col-md-6">{{ str_limit($pack->description, 150) }}</td>
<td class="middle"><a href="{{ route('admin.nests.egg.view', $pack->egg->id) }}">{{ $pack->egg->name }}</a></td>
<td class="middle text-center">{{ $pack->servers_count }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@if ($packs->hasPages())
<div class="box-footer with-border">
<div class="col-md-12 text-center">{!! $packs->appends(['query' => Request::input('query')])->render() !!}</div>
</div>
@endif
</div>
</div>
</div>
@endsection

View file

@ -1,47 +0,0 @@
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="{{ route('admin.packs.new') }}" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Install Pack from Template</h4>
</div>
<div class="modal-body">
<div class="well" style="margin-bottom:0">
<div class="row">
<div class="col-md-12">
<label for="pEggIdModal" class="form-label">Associated Egg:</label>
<select id="pEggIdModal" name="egg_id" class="form-control">
@foreach($nests as $nest)
<optgroup label="{{ $nest->name }}">
@foreach($nest->eggs as $egg)
<option value="{{ $egg->id }}">{{ $egg->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="text-muted small">The Egg that this pack is associated with. Only servers that are assigned this Egg will be able to access this pack.</p>
</div>
</div>
<div class="row" style="margin-top:15px;">
<div class="col-md-12">
<div class="row">
<div class="form-group col-md-12">
<label class="control-label">Package Archive:</label>
<input name="file_upload" type="file" accept=".zip,.json, application/json, application/zip" />
<p class="text-muted"><small>This file should be either the <code>.json</code> template file, or a <code>.zip</code> pack archive containing <code>archive.tar.gz</code> and <code>import.json</code> within.<br /><br />This server is currently configured with the following limits: <code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code> and <code>post_max_size={{ ini_get('post_max_size') }}</code>. If your file is larger than either of those values this request will fail.</small></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
{!! csrf_field() !!}
<button type="submit" name="action" value="from_template" class="btn btn-primary btn-sm">Install</button>
<button type="button" class="btn btn-default btn-sm pull-left" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -1,144 +0,0 @@
{{-- 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 --}}
@extends('layouts.admin')
@section('title')
Packs &rarr; New
@endsection
@section('content-header')
<h1>New Pack<small>Create a new pack on the system.</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.packs') }}">Packs</a></li>
<li class="active">New</li>
</ol>
@endsection
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="nav-tabs-custom nav-tabs-floating">
<ul class="nav nav-tabs">
<li class="active"><a href="{{ route('admin.packs.new') }}">Configure Manually</a></li>
<li><a href="#modal" id="toggleModal">Install From Template</a></li>
</ul>
</div>
</div>
</div>
<form action="{{ route('admin.packs.new') }}" method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Details</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pName" class="form-label">Name</label>
<input name="name" type="text" id="pName" class="form-control" value="{{ old('name') }}" />
<p class="text-muted small">A short but descriptive name of what this pack is. For example, <code>Counter Strike: Source</code> if it is a Counter Strike package.</p>
</div>
<div class="form-group">
<label for="pDescription" class="form-label">Description</label>
<textarea name="description" id="pDescription" class="form-control" rows="8">{{ old('description') }}</textarea>
</div>
<div class="form-group">
<label for="pVersion" class="form-label">Version</label>
<input type="text" name="version" id="pVersion" class="form-control" value="{{ old('version') }}" />
<p class="text-muted small">The version of this package, or the version of the files contained within the package.</p>
</div>
<div class="form-group">
<label for="pEggId" class="form-label">Associated Egg</label>
<select id="pEggId" name="egg_id" class="form-control">
@foreach($nests as $nest)
<optgroup label="{{ $nest->name }}">
@foreach($nest->eggs as $egg)
<option value="{{ $egg->id }}">{{ $egg->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="text-muted small">The option that this pack is associated with. Only servers that are assigned this option will be able to access this pack.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Configuration</h3>
</div>
<div class="box-body">
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pSelectable" name="selectable" type="checkbox" value="1" checked/>
<label for="pSelectable">
Selectable
</label>
</div>
<p class="text-muted small">Check this box if user should be able to select this pack to install on their servers.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pVisible" name="visible" type="checkbox" value="1" checked/>
<label for="pVisible">
Visible
</label>
</div>
<p class="text-muted small">Check this box if this pack is visible in the dropdown menu. If this pack is assigned to a server it will be visible regardless of this setting.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-warning no-margin-bottom">
<input id="pLocked" name="locked" type="checkbox" value="1"/>
<label for="pLocked">
Locked
</label>
</div>
<p class="text-muted small">Check this box if servers assigned this pack should not be able to switch to a different pack.</p>
</div>
<hr />
<div class="form-group no-margin-bottom">
<label for="pFileUpload" class="form-label">Pack Archive</label>
<input type="file" accept=".tar.gz, application/gzip" name="file_upload" class="well well-sm" style="width:100%"/>
<p class="text-muted small">This package file must be a <code>.tar.gz</code> archive of pack files to be decompressed into the server folder.</p>
<p class="text-muted small">If your file is larger than <code>50MB</code> it is recommended to upload it using SFTP. Once you have added this pack to the system, a path will be provided where you should upload the file.</p>
<div class="callout-info callout-slim no-margin-bottom">
<p class="text-muted small"><strong>This node is currently configured with the following limits:</strong><br /><code>upload_max_filesize={{ ini_get('upload_max_filesize') }}</code><br /><code>post_max_size={{ ini_get('post_max_size') }}</code><br /><br />If your file is larger than either of those values this request will fail.</p>
</div>
</div>
</div>
<div class="box-footer with-border">
{!! csrf_field() !!}
<button class="btn btn-sm btn-success pull-right" type="submit">Create Pack</button>
</div>
</div>
</div>
</div>
</form>
@endsection
@section('footer-scripts')
@parent
<script>
$('#pEggId').select2();
$('#toggleModal').on('click', function (event) {
event.preventDefault();
$.ajax({
method: 'GET',
url: '/admin/packs/new/template',
headers: { 'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content') },
}).fail(function (jqXhr) {
console.error(jqXhr);
alert('There was an error trying to create the upload modal.');
}).done(function (data) {
$(data).modal();
$('#pEggIdModal').select2();
});
});
</script>
@endsection

View file

@ -1,154 +0,0 @@
{{-- 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 --}}
@extends('layouts.admin')
@section('title')
Packs &rarr; View &rarr; {{ $pack->name }}
@endsection
@section('content-header')
<h1>{{ $pack->name }}<small>{{ str_limit($pack->description, 60) }}</small></h1>
<ol class="breadcrumb">
<li><a href="{{ route('admin.index') }}">Admin</a></li>
<li><a href="{{ route('admin.packs') }}">Packs</a></li>
<li class="active">{{ $pack->name }}</li>
</ol>
@endsection
@section('content')
<form action="{{ route('admin.packs.view', $pack->id) }}" method="POST">
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Details</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pName" class="form-label">Name</label>
<input name="name" type="text" id="pName" class="form-control" value="{{ $pack->name }}" />
<p class="text-muted small">A short but descriptive name of what this pack is. For example, <code>Counter Strike: Source</code> if it is a Counter Strike package.</p>
</div>
<div class="form-group">
<label for="pDescription" class="form-label">Description</label>
<textarea name="description" id="pDescription" class="form-control" rows="8">{{ $pack->description }}</textarea>
</div>
<div class="form-group">
<label for="pVersion" class="form-label">Version</label>
<input type="text" name="version" id="pVersion" class="form-control" value="{{ $pack->version }}" />
<p class="text-muted small">The version of this package, or the version of the files contained within the package.</p>
</div>
<div class="form-group">
<label class="form-label">Storage Location</label>
<input type="text" class="form-control" readonly value="{{ storage_path('app/packs/' . $pack->uuid) }}">
<p class="text-muted small">If you would like to modify the stored pack you will need to upload a new <code>archive.tar.gz</code> to the location defined above.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Pack Configuration</h3>
</div>
<div class="box-body">
<div class="form-group">
<label for="pEggId" class="form-label">Associated Option</label>
<select id="pEggId" name="egg_id" class="form-control">
@foreach($nests as $nest)
<optgroup label="{{ $nest->name }}">
@foreach($nest->eggs as $egg)
<option value="{{ $egg->id }}" {{ $pack->egg_id !== $egg->id ?: 'selected' }}>{{ $egg->name }}</option>
@endforeach
</optgroup>
@endforeach
</select>
<p class="text-muted small">The option that this pack is associated with. Only servers that are assigned this option will be able to access this pack. This assigned option <em>cannot</em> be changed if servers are attached to this pack.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pSelectable" name="selectable" type="checkbox" value="1" {{ ! $pack->selectable ?: 'checked' }}/>
<label for="pSelectable">
Selectable
</label>
</div>
<p class="text-muted small">Check this box if user should be able to select this pack to install on their servers.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pVisible" name="visible" type="checkbox" value="1" {{ ! $pack->visible ?: 'checked' }}/>
<label for="pVisible">
Visible
</label>
</div>
<p class="text-muted small">Check this box if this pack is visible in the dropdown menu. If this pack is assigned to a server it will be visible regardless of this setting.</p>
</div>
<div class="form-group">
<div class="checkbox checkbox-warning no-margin-bottom">
<input id="pLocked" name="locked" type="checkbox" value="1" {{ ! $pack->locked ?: 'checked' }}/>
<label for="pLocked">
Locked
</label>
</div>
<p class="text-muted small">Check this box if servers assigned this pack should not be able to switch to a different pack.</p>
</div>
</div>
<div class="box-footer with-border">
{!! csrf_field() !!}
<button name="_method" value="PATCH" class="btn btn-sm btn-primary pull-right" type="submit">Save</button>
<button name="_method" value="DELETE" class="btn btn-sm btn-danger pull-left muted muted-hover" type="submit"><i class="fa fa-trash-o"></i></button>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">Servers Using This Pack</h3>
</div>
<div class="box-body no-padding table-responsive">
<table class="table table-hover">
<tr>
<th>ID</th>
<th>Server Name</th>
<th>Node</th>
<th>Owner</th>
</tr>
@foreach($pack->servers as $server)
<tr>
<td><code>{{ $server->uuidShort }}</code></td>
<td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
<td><a href="{{ route('admin.nodes.view', $server->node->id) }}">{{ $server->node->name }}</a></td>
<td><a href="{{ route('admin.users.view', $server->user->id) }}">{{ $server->user->email }}</a></td>
</tr>
@endforeach
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-5 col-md-offset-7 col-xs-offset-6">
<form action="{{ route('admin.packs.view.export', $pack->id) }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm btn-success pull-right">Export</button>
</form>
<form action="{{ route('admin.packs.view.export', ['pack' => $pack->id, 'files' => 'with-files']) }}" method="POST">
{!! csrf_field() !!}
<button type="submit" class="btn btn-sm pull-right muted muted-hover" style="margin-right:10px;">Export with Archive</button>
</form>
</div>
</div>
@endsection
@section('footer-scripts')
@parent
<script>
$('#pEggId').select2();
</script>
@endsection

View file

@ -244,20 +244,13 @@
<select id="pEggId" name="egg_id" class="form-control"></select>
<p class="small text-muted no-margin">Select the Egg that will define how this server should operate.</p>
</div>
<div class="form-group col-xs-12">
<label for="pPackId">Data Pack</label>
<select id="pPackId" name="pack_id" class="form-control"></select>
<p class="small text-muted no-margin">Select a data pack to be automatically installed on this server when first created.</p>
</div>
<div class="form-group col-xs-12">
<div class="checkbox checkbox-primary no-margin-bottom">
<input type="checkbox" id="pSkipScripting" name="skip_scripts" value="1" {{ \Pterodactyl\Helpers\Utilities::checked('skip_scripts', 0) }} />
<label for="pSkipScripting" class="strong">Skip Egg Install Script</label>
</div>
<p class="small text-muted no-margin">If the selected Egg has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.</p>
<p class="small text-muted no-margin">If the selected Egg has an install script attached to it, the script will run during the install. If you would like to skip this step, check this box.</p>
</div>
</div>
</div>
@ -384,12 +377,6 @@
$('#pEggId').val('{{ old('egg_id') }}').change();
@endif
// END Persist 'Egg' select2
// Persist 'Data Pack' select2
@if (old('pack_id'))
$('#pPackId').val('{{ old('pack_id') }}').change();
@endif
// END Persist 'Data Pack' select2
@endif
// END Persist 'Nest' select2
});

View file

@ -28,7 +28,7 @@
<h3 class="box-title">Reinstall Server</h3>
</div>
<div class="box-body">
<p>This will reinstall the server with the assigned pack and service scripts. <strong>Danger!</strong> This could overwrite server data.</p>
<p>This will reinstall the server with the assigned service scripts. <strong>Danger!</strong> This could overwrite server data.</p>
</div>
<div class="box-footer">
@if($server->installed === 1)

View file

@ -54,7 +54,7 @@
<div class="col-xs-12">
<p class="small text-danger">
Changing any of the below values will result in the server processing a re-install command. The server will be stopped and will then proceed.
If you are changing the pack, existing data <em>may</em> be overwritten. If you would like the service scripts to not run, ensure the box is checked at the bottom.
If you would like the service scripts to not run, ensure the box is checked at the bottom.
</p>
<p class="small text-danger">
<strong>This is a destructive operation in many cases. This server will be stopped immediately in order for this action to proceed.</strong>
@ -78,17 +78,12 @@
<select name="egg_id" id="pEggId" class="form-control"></select>
<p class="small text-muted no-margin">Select the Egg that will provide processing data for this server.</p>
</div>
<div class="form-group col-xs-12">
<label for="pPackId">Data Pack</label>
<select name="pack_id" id="pPackId" class="form-control"></select>
<p class="small text-muted no-margin">Select a data pack to be automatically installed on this server when first created.</p>
</div>
<div class="form-group col-xs-12">
<div class="checkbox checkbox-primary no-margin-bottom">
<input id="pSkipScripting" name="skip_scripts" type="checkbox" value="1" @if($server->skip_scripts) checked @endif />
<label for="pSkipScripting" class="strong">Skip Egg Install Script</label>
</div>
<p class="small text-muted no-margin">If the selected Egg has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.</p>
<p class="small text-muted no-margin">If the selected Egg has an install script attached to it, the script will run during install. If you would like to skip this step, check this box.</p>
</div>
</div>
</div>
@ -117,7 +112,6 @@
{!! Theme::js('vendor/lodash/lodash.js') !!}
<script>
$(document).ready(function () {
$('#pPackId').select2({placeholder: 'Select a Service Pack'});
$('#pEggId').select2({placeholder: 'Select a Nest Egg'}).on('change', function () {
var selectedEgg = _.isNull($(this).val()) ? $(this).find('option').first().val() : $(this).val();
var parentChain = _.get(Pterodactyl.nests, $("#pNestId").val());
@ -135,21 +129,6 @@
$('#pDefaultStartupCommand').val(_.get(objectChain, 'startup'));
}
$('#pPackId').html('').select2({
data: [{id: '0', text: 'No Service Pack'}].concat(
$.map(_.get(objectChain, 'packs', []), function (item, i) {
return {
id: item.id,
text: item.name + ' (' + item.version + ')',
};
})
),
});
if (Pterodactyl.server.pack_id !== null) {
$('#pPackId').val(Pterodactyl.server.pack_id);
}
$('#appendVariablesTo').html('');
$.each(_.get(objectChain, 'variables', []), function (i, item) {
var setValue = _.get(Pterodactyl.server_variables, item.env_variable, item.default_value);

View file

@ -127,11 +127,6 @@
<i class="fa fa-th-large"></i> <span>Nests</span>
</a>
</li>
<li class="{{ ! starts_with(Route::currentRouteName(), 'admin.packs') ?: 'active' }}">
<a href="{{ route('admin.packs') }}">
<i class="fa fa-archive"></i> <span>Packs</span>
</a>
</li>
</ul>
</section>
</aside>

View file

@ -224,25 +224,3 @@ Route::group(['prefix' => 'nests'], function () {
Route::delete('/egg/{egg}', 'Nests\EggController@destroy');
Route::delete('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@destroy');
});
/*
|--------------------------------------------------------------------------
| Pack Controller Routes
|--------------------------------------------------------------------------
|
| Endpoint: /admin/packs
|
*/
Route::group(['prefix' => 'packs'], function () {
Route::get('/', 'PackController@index')->name('admin.packs');
Route::get('/new', 'PackController@create')->name('admin.packs.new');
Route::get('/new/template', 'PackController@newTemplate')->name('admin.packs.new.template');
Route::get('/view/{pack}', 'PackController@view')->name('admin.packs.view');
Route::post('/new', 'PackController@store');
Route::post('/view/{pack}/export/{files?}', 'PackController@export')->name('admin.packs.view.export');
Route::patch('/view/{pack}', 'PackController@update');
Route::delete('/view/{pack}', 'PackController@destroy');
});

View file

@ -118,7 +118,6 @@ abstract class ApplicationApiIntegrationTestCase extends IntegrationTestCase
'r_eggs' => AdminAcl::READ | AdminAcl::WRITE,
'r_database_hosts' => AdminAcl::READ | AdminAcl::WRITE,
'r_server_databases' => AdminAcl::READ | AdminAcl::WRITE,
'r_packs' => AdminAcl::READ | AdminAcl::WRITE,
], $permissions));
}

View file

@ -1,150 +0,0 @@
<?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 Tests\Unit\Services\Packs;
use ZipArchive;
use Mockery as m;
use Tests\TestCase;
use phpmock\phpunit\PHPMock;
use Pterodactyl\Models\Pack;
use Illuminate\Contracts\Filesystem\Factory;
use Pterodactyl\Services\Packs\ExportPackService;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException;
class ExportPackServiceTest extends TestCase
{
use PHPMock;
/**
* @var \ZipArchive
*/
protected $archive;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Packs\ExportPackService
*/
protected $service;
/**
* @var \Illuminate\Contracts\Filesystem\Factory
*/
protected $storage;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->archive = m::mock(ZipArchive::class);
$this->repository = m::mock(PackRepositoryInterface::class);
$this->storage = m::mock(Factory::class);
$this->service = new ExportPackService($this->storage, $this->repository, $this->archive);
}
/**
* Provide standard data to all tests.
*/
protected function setupTestData()
{
$this->model = factory(Pack::class)->make();
$this->json = [
'name' => $this->model->name,
'version' => $this->model->version,
'description' => $this->model->description,
'selectable' => $this->model->selectable,
'visible' => $this->model->visible,
'locked' => $this->model->locked,
];
}
/**
* Test that an archive of the entire pack can be exported.
*/
public function testFilesAreBundledIntoZipWhenRequested()
{
$this->setupTestData();
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam')
->expects($this->once())->willReturn('/tmp/myfile.test');
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fopen')->expects($this->never());
$this->archive->shouldReceive('open')->with('/tmp/myfile.test', $this->archive::CREATE)->once()->andReturnSelf();
$this->storage->shouldReceive('disk->files')->with('packs/' . $this->model->uuid)->once()->andReturn(['file_one']);
$this->archive->shouldReceive('addFile')->with(storage_path('app/file_one'), 'file_one')->once()->andReturnSelf();
$this->archive->shouldReceive('addFromString')->with('import.json', json_encode($this->json, JSON_PRETTY_PRINT))->once()->andReturnSelf();
$this->archive->shouldReceive('close')->withNoArgs()->once()->andReturnNull();
$response = $this->service->handle($this->model, true);
$this->assertEquals('/tmp/myfile.test', $response);
}
/**
* Test that the pack configuration can be saved as a json file.
*/
public function testPackConfigurationIsSavedAsJsonFile()
{
$this->setupTestData();
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam')
->expects($this->once())->willReturn('/tmp/myfile.test');
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fopen')->expects($this->once())->wilLReturn('fp');
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fwrite')
->expects($this->once())->with('fp', json_encode($this->json, JSON_PRETTY_PRINT))->willReturn(null);
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fclose')
->expects($this->once())->with('fp')->willReturn(null);
$response = $this->service->handle($this->model);
$this->assertEquals('/tmp/myfile.test', $response);
}
/**
* Test that a model ID can be passed in place of the model itself.
*/
public function testPackIdCanBePassedInPlaceOfModel()
{
$this->setupTestData();
$this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model);
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam')->expects($this->once())->willReturn('/tmp/myfile.test');
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fopen')->expects($this->once())->wilLReturn(null);
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fwrite')->expects($this->once())->willReturn(null);
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'fclose')->expects($this->once())->willReturn(null);
$response = $this->service->handle($this->model->id);
$this->assertEquals('/tmp/myfile.test', $response);
}
/**
* Test that an exception is thrown when a ZipArchive cannot be created.
*/
public function testExceptionIsThrownIfZipArchiveCannotBeCreated()
{
$this->expectException(ZipArchiveCreationException::class);
$this->setupTestData();
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam')
->expects($this->once())->willReturn('/tmp/myfile.test');
$this->archive->shouldReceive('open')->once()->andReturn(false);
$this->service->handle($this->model, true);
}
}

View file

@ -1,182 +0,0 @@
<?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 Tests\Unit\Services\Packs;
use Exception;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Pack;
use Tests\Traits\MocksUuids;
use Illuminate\Http\UploadedFile;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Packs\PackCreationService;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
class PackCreationServiceTest extends TestCase
{
use MocksUuids;
/**
* @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
*/
protected $connection;
/**
* @var \Illuminate\Http\UploadedFile|\Mockery\Mock
*/
protected $file;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Packs\PackCreationService
*/
protected $service;
/**
* @var \Illuminate\Contracts\Filesystem\Factory|\Mockery\Mock
*/
protected $storage;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->file = m::mock(UploadedFile::class);
$this->repository = m::mock(PackRepositoryInterface::class);
$this->storage = m::mock(Factory::class);
$this->service = new PackCreationService($this->connection, $this->storage, $this->repository);
}
/**
* Test that a pack is created when no file upload is provided.
*/
public function testPackIsCreatedWhenNoUploadedFileIsPassed()
{
$model = factory(Pack::class)->make();
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('create')->with([
'uuid' => $this->getKnownUuid(),
'selectable' => false,
'visible' => false,
'locked' => false,
'test-data' => 'value',
])->once()->andReturn($model);
$this->storage->shouldReceive('disk->makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$response = $this->service->handle(['test-data' => 'value']);
$this->assertInstanceOf(Pack::class, $response);
$this->assertEquals($model, $response);
}
/**
* Test that a pack can be created when an uploaded file is provided.
*
* @dataProvider mimetypeProvider
*/
public function testPackIsCreatedWhenUploadedFileIsProvided($mime)
{
$model = factory(Pack::class)->make();
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('create')->with([
'uuid' => $this->getKnownUuid(),
'selectable' => false,
'visible' => false,
'locked' => false,
'test-data' => 'value',
])->once()->andReturn($model);
$this->storage->shouldReceive('disk->makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull();
$this->file->shouldReceive('storeAs')->with('packs/' . $model->uuid, 'archive.tar.gz')->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$response = $this->service->handle(['test-data' => 'value'], $this->file);
$this->assertInstanceOf(Pack::class, $response);
$this->assertEquals($model, $response);
}
/**
* Test that an exception is thrown if the file upload is not valid.
*/
public function testExceptionIsThrownIfInvalidUploadIsProvided()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false);
try {
$this->service->handle([], $this->file);
} catch (Exception $exception) {
$this->assertInstanceOf(InvalidFileUploadException::class, $exception);
$this->assertEquals(trans('exceptions.packs.invalid_upload'), $exception->getMessage());
}
}
/**
* Test that an exception is thrown when an invalid mimetype is provided.
*
* @dataProvider invalidMimetypeProvider
*/
public function testExceptionIsThrownIfInvalidMimetypeIsFound($mime)
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime);
try {
$this->service->handle([], $this->file);
} catch (InvalidFileMimeTypeException $exception) {
$this->assertEquals(trans('exceptions.packs.invalid_mime', [
'type' => implode(', ', PackCreationService::VALID_UPLOAD_TYPES),
]), $exception->getMessage());
}
}
/**
* Return an array of valid mimetypes to test against.
*
* @return array
*/
public function mimetypeProvider()
{
return [
['application/gzip'],
['application/x-gzip'],
];
}
/**
* Provide invalid mimetypes to test exceptions against.
*
* @return array
*/
public function invalidMimetypeProvider()
{
return [
['application/zip'],
['text/plain'],
['image/jpeg'],
];
}
}

View file

@ -1,120 +0,0 @@
<?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 Tests\Unit\Services\Packs;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Pack;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Packs\PackDeletionService;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class PackDeletionServiceTest extends TestCase
{
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $connection;
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $serverRepository;
/**
* @var \Pterodactyl\Services\Packs\PackDeletionService
*/
protected $service;
/**
* @var \Illuminate\Contracts\Filesystem\Factory
*/
protected $storage;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
$this->repository = m::mock(PackRepositoryInterface::class);
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
$this->storage = m::mock(Factory::class);
$this->service = new PackDeletionService(
$this->connection,
$this->storage,
$this->repository,
$this->serverRepository
);
}
/**
* Test that a pack is deleted.
*/
public function testPackIsDeleted()
{
$model = factory(Pack::class)->make();
$this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(0);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('delete')->with($model->id)->once()->andReturn(1);
$this->storage->shouldReceive('disk')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('deleteDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->service->handle($model);
}
/**
* Test that a pack ID can be passed in place of the model.
*/
public function testPackIdCanBePassedInPlaceOfModel()
{
$model = factory(Pack::class)->make();
$this->repository->shouldReceive('setColumns')->with(['id', 'uuid'])->once()->andReturnSelf()
->shouldReceive('find')->with($model->id)->once()->andReturn($model);
$this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(0);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('delete')->with($model->id)->once()->andReturn(1);
$this->storage->shouldReceive('disk')->withNoArgs()->once()->andReturnSelf()
->shouldReceive('deleteDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->service->handle($model->id);
}
/**
* Test that an exception gets thrown if a server is attached to a pack.
*/
public function testExceptionIsThrownIfServerIsAttachedToPack()
{
$model = factory(Pack::class)->make();
$this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(1);
try {
$this->service->handle($model);
} catch (HasActiveServersException $exception) {
$this->assertEquals(trans('exceptions.packs.delete_has_servers'), $exception->getMessage());
}
}
}

View file

@ -1,99 +0,0 @@
<?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 Tests\Unit\Services\Packs;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Pack;
use Pterodactyl\Services\Packs\PackUpdateService;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class PackUpdateServiceTest extends TestCase
{
/**
* @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface|\Mockery\Mock
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
*/
protected $serverRepository;
/**
* @var \Pterodactyl\Services\Packs\PackUpdateService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->repository = m::mock(PackRepositoryInterface::class);
$this->serverRepository = m::mock(ServerRepositoryInterface::class);
$this->service = new PackUpdateService($this->repository, $this->serverRepository);
}
/**
* Test that a pack is updated.
*/
public function testPackIsUpdated()
{
$model = factory(Pack::class)->make();
$this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
'locked' => false,
'visible' => false,
'selectable' => false,
'test-data' => 'value',
])->once()->andReturn(1);
$this->assertEquals(1, $this->service->handle($model, ['test-data' => 'value']));
}
/**
* Test that an exception is thrown if the pack option ID is changed while servers are using the pack.
*/
public function testExceptionIsThrownIfModifyingEggIdWhenServersAreAttached()
{
$model = factory(Pack::class)->make();
$this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(1);
try {
$this->service->handle($model, ['egg_id' => 0]);
} catch (HasActiveServersException $exception) {
$this->assertEquals(trans('exceptions.packs.update_has_servers'), $exception->getMessage());
}
}
/**
* Test that an ID for a pack can be passed in place of the model.
*/
public function testPackIdCanBePassedInPlaceOfModel()
{
$model = factory(Pack::class)->make();
$this->repository->shouldReceive('setColumns')->with(['id', 'egg_id'])->once()->andReturnSelf()
->shouldReceive('find')->with($model->id)->once()->andReturn($model);
$this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
'locked' => false,
'visible' => false,
'selectable' => false,
'test-data' => 'value',
])->once()->andReturn(1);
$this->assertEquals(1, $this->service->handle($model->id, ['test-data' => 'value']));
}
}

View file

@ -1,245 +0,0 @@
<?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 Tests\Unit\Services\Packs;
use ZipArchive;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Pack;
use Illuminate\Http\UploadedFile;
use Pterodactyl\Services\Packs\PackCreationService;
use Pterodactyl\Services\Packs\TemplateUploadService;
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException;
use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException;
use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException;
use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException;
class TemplateUploadServiceTest extends TestCase
{
const JSON_FILE_CONTENTS = '{"test_content": "value"}';
/**
* @var \ZipArchive|\Mockery\Mock
*/
protected $archive;
/**
* @var \Pterodactyl\Services\Packs\PackCreationService|\Mockery\Mock
*/
protected $creationService;
/**
* @var \Illuminate\Http\UploadedFile|\Mockery\Mock
*/
protected $file;
/**
* @var \Pterodactyl\Services\Packs\TemplateUploadService
*/
protected $service;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->archive = m::mock(ZipArchive::class);
$this->creationService = m::mock(PackCreationService::class);
$this->file = m::mock(UploadedFile::class);
$this->service = new TemplateUploadService($this->creationService, $this->archive);
}
/**
* Test that a JSON file can be processed and turned into a pack.
*
* @dataProvider jsonMimetypeProvider
*/
public function testJsonFileIsProcessed($mime)
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn($mime);
$this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(128);
$this->file->shouldReceive('openFile->fread')->with(128)->once()->andReturn(self::JSON_FILE_CONTENTS);
$this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'egg_id' => 1])
->once()->andReturn(factory(Pack::class)->make());
$this->assertInstanceOf(Pack::class, $this->service->handle(1, $this->file));
}
/**
* Test that a zip file can be processed.
*/
public function testZipfileIsProcessed()
{
$model = factory(Pack::class)->make();
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip');
$this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/real');
$this->archive->shouldReceive('open')->with('/test/real')->once()->andReturn(true);
$this->archive->shouldReceive('locateName')->with('import.json')->once()->andReturn(true);
$this->archive->shouldReceive('locateName')->with('archive.tar.gz')->once()->andReturn(true);
$this->archive->shouldReceive('getFromName')->with('import.json')->once()->andReturn(self::JSON_FILE_CONTENTS);
$this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'egg_id' => 1])
->once()->andReturn($model);
$this->archive->shouldReceive('extractTo')->with(storage_path('app/packs/' . $model->uuid), 'archive.tar.gz')
->once()->andReturn(true);
$this->archive->shouldReceive('close')->withNoArgs()->once()->andReturnNull();
$this->assertInstanceOf(Pack::class, $this->service->handle(1, $this->file));
}
/**
* Test that an exception is thrown if the file upload is invalid.
*/
public function testExceptionIsThrownIfFileUploadIsInvalid()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false);
try {
$this->service->handle(1, $this->file);
} catch (InvalidFileUploadException $exception) {
$this->assertEquals(trans('exceptions.packs.invalid_upload'), $exception->getMessage());
}
}
/**
* Test that an invalid mimetype throws an exception.
*
* @dataProvider invalidMimetypeProvider
*/
public function testExceptionIsThrownIfMimetypeIsInvalid($mime)
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime);
try {
$this->service->handle(1, $this->file);
} catch (InvalidFileMimeTypeException $exception) {
$this->assertEquals(trans('exceptions.packs.invalid_mime', [
'type' => implode(', ', TemplateUploadService::VALID_UPLOAD_TYPES),
]), $exception->getMessage());
}
}
/**
* Test that an exception is thrown if the zip is unreadable.
*/
public function testExceptionIsThrownIfZipArchiveIsUnreadable()
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip');
$this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/path');
$this->archive->shouldReceive('open')->with('/test/path')->once()->andReturn(false);
try {
$this->service->handle(1, $this->file);
} catch (UnreadableZipArchiveException $exception) {
$this->assertEquals(trans('exceptions.packs.unreadable'), $exception->getMessage());
}
}
/**
* Test that a zip missing the required files throws an exception.
*
* @dataProvider filenameProvider
*/
public function testExceptionIsThrownIfZipDoesNotContainProperFiles($a, $b)
{
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip');
$this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/path');
$this->archive->shouldReceive('open')->with('/test/path')->once()->andReturn(true);
$this->archive->shouldReceive('locateName')->with('import.json')->once()->andReturn($a);
if ($a) {
$this->archive->shouldReceive('locateName')->with('archive.tar.gz')->once()->andReturn($b);
}
try {
$this->service->handle(1, $this->file);
} catch (InvalidPackArchiveFormatException $exception) {
$this->assertEquals(trans('exceptions.packs.invalid_archive_exception'), $exception->getMessage());
}
}
/**
* Test that an exception is thrown if an archive cannot be extracted from the zip file.
*/
public function testExceptionIsThrownIfArchiveCannotBeExtractedFromZip()
{
$model = factory(Pack::class)->make();
$this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true);
$this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn('application/zip');
$this->file->shouldReceive('getRealPath')->withNoArgs()->once()->andReturn('/test/real');
$this->archive->shouldReceive('open')->once()->andReturn(true);
$this->archive->shouldReceive('locateName')->twice()->andReturn(true);
$this->archive->shouldReceive('getFromName')->once()->andReturn(self::JSON_FILE_CONTENTS);
$this->creationService->shouldReceive('handle')->once()->andReturn($model);
$this->archive->shouldReceive('extractTo')->once()->andReturn(false);
try {
$this->service->handle(1, $this->file);
} catch (ZipExtractionException $exception) {
$this->assertEquals(trans('exceptions.packs.zip_extraction'), $exception->getMessage());
}
}
/**
* Provide valid JSON mimetypes to use in tests.
*
* @return array
*/
public function jsonMimetypeProvider()
{
return [
['text/plain'],
['application/json'],
];
}
/**
* Return invalid mimetypes for testing.
*
* @return array
*/
public function invalidMimetypeProvider()
{
return [
['application/gzip'],
['application/x-gzip'],
['image/jpeg'],
];
}
/**
* Return values for archive->locateName function, import.json and archive.tar.gz respectively.
*
* @return array
*/
public function filenameProvider()
{
return [
[true, false],
[false, true],
[false, false],
];
}
}

View file

@ -42,7 +42,6 @@ class ServerConfigurationStructureServiceTest extends TestCase
{
/** @var \Pterodactyl\Models\Server $model */
$model = factory(Server::class)->make();
$model->setRelation('pack', null);
$model->setRelation('allocation', factory(Allocation::class)->make());
$model->setRelation('allocations', collect(factory(Allocation::class)->times(2)->make()));
$model->setRelation('egg', factory(Egg::class)->make());
@ -82,7 +81,6 @@ class ServerConfigurationStructureServiceTest extends TestCase
$this->assertSame([
'egg' => $model->egg->uuid,
'pack' => null,
'skip_scripts' => $model->skip_scripts,
], $response['service']);

View file

@ -140,12 +140,10 @@ class StartupModificationServiceTest extends TestCase
'installed' => 0,
'nest_id' => $eggModel->nest_id,
'egg_id' => $eggModel->id,
'pack_id' => 789,
'image' => 'docker:image',
]))->once()->andReturn($model);
$this->repository->shouldReceive('getDaemonServiceData')->with($model, true)->once()->andReturn([
'egg' => 'abcd1234',
'pack' => 'xyz987',
]);
$this->environmentService->shouldReceive('handle')->with($model)->once()->andReturn(['env']);
@ -158,7 +156,6 @@ class StartupModificationServiceTest extends TestCase
],
'service' => [
'egg' => 'abcd1234',
'pack' => 'xyz987',
'skip_scripts' => false,
],
])->once()->andReturn(new Response);
@ -170,7 +167,6 @@ class StartupModificationServiceTest extends TestCase
$response = $service->handle($model, [
'docker_image' => 'docker:image',
'egg_id' => $eggModel->id,
'pack_id' => 789,
'environment' => ['test' => 'abcd1234'],
]);