diff --git a/README.md b/README.md index 3fab319b8..a88f18036 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,44 @@ [![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io) [![Build status](https://img.shields.io/travis/pterodactyl/panel/develop.svg?style=flat-square)](https://travis-ci.org/pterodactyl/panel) -[![StyleCI](https://styleci.io/repos/47508644/shield?branch=develop)](https://styleci.io/repos/47508644) [![Codecov](https://img.shields.io/codecov/c/github/pterodactyl/panel/develop.svg?style=flat-square)](https://codecov.io/gh/Pterodactyl/Panel) [![Discord](https://img.shields.io/discord/122900397965705216.svg?style=flat-square&label=Discord)](https://pterodactyl.io/discord) # Pterodactyl Panel +Pterodactyl is an open-source game server management panel built with PHP 7, React, and Go. Designed with security +in mind, Pterodactyl runs all game servers in isolated Docker container while exposing a beautiful and intuitive +UI to end users. -Pterodactyl is the open-source game server management panel built with PHP7, Nodejs, and Go. Designed with security in mind, Pterodactyl runs all game servers in isolated Docker containers while exposing a beautiful and intuitive UI to administrators and users. -What more are you waiting for? Make game servers a first class citizen on your platform today. +Stop settling for less. Make game servers a first class citizen on your platform. -![Image](https://cdn.pterodactyl.io/site-assets/mockup-macbook-grey.png) +![Image](https://cdn.pterodactyl.io/site-assets/pterodactyl_v1_demo.gif) ## Sponsors -I would like to extend my sincere thanks to the following sponsors for funding Pterodactyl's developement. [Interested -in becoming a sponsor?](https://github.com/sponsors/DaneEveritt) +I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's developement. +[Interested in becoming a sponsor?](https://github.com/sponsors/DaneEveritt) -#### [BloomVPS](https://bloomvps.com) -> BloomVPS offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly -> unbeatable prices on high-performance hosting. +| Company | About | +| ------- | ----- | +| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. | +| [**VersatileNode**](https://versatilenode.com/) | Looking to host a minecraft server, vps, or a website? VersatileNode is one of the most affordable hosting providers to provide quality yet cheap services with incredible support. | +| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. | +| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. | +| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | +| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | -#### [VersatileNode](https://versatilenode.com/) -> Looking to host a minecraft server, vps, or a website? VersatileNode is one of the most affordable hosting providers -> to provide quality yet cheap services with incredible support. - -#### [MineStrator](https://minestrator.com/) -> Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord -> trust us. - -#### [DedicatedMC](https://dedicatedmc.io/) -> DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance -> and giving you the best performance money can buy. - -#### [Skynode](https://www.skynode.pro/) -> Skynode provides blazing fast game servers along with a top notch user experience. Whatever our clients are looking -> for, we're able to provide it! - -#### [XCORE-SERVER.de](https://xcore-server.de) -> XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well known for eSports Gaming. - -## Support & Documentation -Support for using Pterodactyl can be found on our [Documentation Website](https://pterodactyl.io/project/introduction.html), [Guides Website](https://pterodactyl.io/community/about.html), or via our [Discord Chat](https://discord.gg/QRDZvVm). +## Documentation +* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) +* [Wings Documentation](https://pterodactyl.io/wings/1.0/installing.html) +* [Community Guides](https://pterodactyl.io/community/about.html) +* Or, get additional help [via Discord](https://discord.gg/pterodactyl) ### Supported Games -We support a huge variety of games by utilizing Docker containers to isolate each instance, giving you the power to host your games across the world without having to bloat each physical machine with additional dependencies. +We support a huge variety of games by utilizing Docker containers to isolate each instance, giving you the power to +host your games across the world without having to bloat each physical machine with additional dependencies. Some of our core supported games include: -* Minecraft — including Spigot, Sponge, Bungeecord, Waterfall, and more +* Minecraft — including Paper, Sponge, Bungeecord, Waterfall, and more * Rust * Terraria * Teamspeak @@ -57,7 +48,8 @@ Some of our core supported games include: * Garry's Mod * ARK: Survival Evolved -In addition to our standard nest of supported games, our community is constantly pushing the limits of this software and there are plenty more games available provided by the community. Some of these games include: +In addition to our standard nest of supported games, our community is constantly pushing the limits of this software +and there are plenty more games available provided by the community. Some of these games include: * Factorio * San Andreas: MP @@ -65,22 +57,13 @@ In addition to our standard nest of supported games, our community is constantly * Squad * FiveM * Xonotic -* Discord ATLBot - -## Credits -This software would not be possible without the work of other open-source authors who provide tools such as: - -[Ace Editor](https://ace.c9.io), [AdminLTE](https://adminlte.io), [Animate.css](http://daneden.github.io/animate.css/), [AnsiUp](https://github.com/drudru/ansi_up), [Async.js](https://github.com/caolan/async), -[Bootstrap](http://getbootstrap.com), [Bootstrap Notify](http://bootstrap-notify.remabledesigns.com), [Chart.js](http://www.chartjs.org), [FontAwesome](http://fontawesome.io), -[FontAwesome Animations](https://github.com/l-lin/font-awesome-animation), [jQuery](http://jquery.com), [Laravel](https://laravel.com), [Lodash](https://lodash.com), -[Select2](https://select2.github.io), [Socket.io](http://socket.io), [Socket.io File Upload](https://github.com/vote539/socketio-file-upload), [SweetAlert](http://t4t5.github.io/sweetalert), -[Typeahead](https://github.com/bassjobsen/Bootstrap-3-Typeahead), and [Particles.js](http://vincentgarreau.com/particles.js). - -Some Javascript and CSS used within the panel is licensed under a `MIT` or `Apache 2.0` license. Please check their respective header files for more information. +* Starmade +* Discord ATLBot, and most other Node.js/Python discord bots +* [and many more...](https://github.com/parkervcp/eggs) ## License ``` -Copyright (c) 2015 - 2018 Dane Everitt . +Copyright (c) 2015 - 2020 Dane Everitt & Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -100,3 +83,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` + +Some Javascript and CSS used within the panel are licensed under a `MIT` or `Apache 2.0` license. Please check their +respective header files for more information. diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index 5f2a50e62..f458b51dc 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -10,6 +10,7 @@ namespace Pterodactyl\Console\Commands\User; use Webmozart\Assert\Assert; +use Pterodactyl\Models\User; use Illuminate\Console\Command; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -40,16 +41,11 @@ class DeleteUserCommand extends Command * DeleteUserCommand constructor. * * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ - public function __construct( - UserDeletionService $deletionService, - UserRepositoryInterface $repository - ) { + public function __construct(UserDeletionService $deletionService) { parent::__construct(); $this->deletionService = $deletionService; - $this->repository = $repository; } /** @@ -59,9 +55,13 @@ class DeleteUserCommand extends Command public function handle() { $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); - Assert::notEmpty($search, 'Search term must be a non-null value, received %s.'); + Assert::notEmpty($search, 'Search term should be an email address, got: %s.'); + + $results = User::query() + ->where('email', 'LIKE', "$search%") + ->where('username', 'LIKE', "$search%") + ->get(); - $results = $this->repository->setSearchTerm($search)->all(); if (count($results) < 1) { $this->error(trans('command/messages.user.no_users_found')); if ($this->input->isInteractive()) { @@ -95,5 +95,7 @@ class DeleteUserCommand extends Command $this->deletionService->handle($deleteUser); $this->info(trans('command/messages.user.deleted')); } + + return; } } diff --git a/app/Contracts/Repository/AllocationRepositoryInterface.php b/app/Contracts/Repository/AllocationRepositoryInterface.php index b02ef66a4..400279733 100644 --- a/app/Contracts/Repository/AllocationRepositoryInterface.php +++ b/app/Contracts/Repository/AllocationRepositoryInterface.php @@ -6,14 +6,6 @@ use Illuminate\Support\Collection; interface AllocationRepositoryInterface extends RepositoryInterface { - /** - * Return all of the unique IPs that exist for a given node. - * - * @param int $node - * @return \Illuminate\Support\Collection - */ - public function getUniqueAllocationIpsForNode(int $node): Collection; - /** * Return all of the allocations that exist for a node that are not currently * allocated. @@ -23,27 +15,6 @@ interface AllocationRepositoryInterface extends RepositoryInterface */ public function getUnassignedAllocationIds(int $node): array; - /** - * Get an array of all allocations that are currently assigned to a given server. - * - * @param int $server - * @return array - */ - public function getAssignedAllocationIds(int $server): array; - - /** - * Return a concatenated result set of node ips that already have at least one - * server assigned to that IP. This allows for filtering out sets for - * dedicated allocation IPs. - * - * If an array of nodes is passed the results will be limited to allocations - * in those nodes. - * - * @param array $nodes - * @return array - */ - public function getDiscardableDedicatedAllocations(array $nodes = []): array; - /** * Return a single allocation from those meeting the requirements. * diff --git a/app/Contracts/Repository/Attributes/SearchableInterface.php b/app/Contracts/Repository/Attributes/SearchableInterface.php deleted file mode 100644 index f1ab6e804..000000000 --- a/app/Contracts/Repository/Attributes/SearchableInterface.php +++ /dev/null @@ -1,38 +0,0 @@ -. - * - * 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 -{ -} diff --git a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php b/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php deleted file mode 100644 index 5e216fed4..000000000 --- a/app/Exceptions/Service/Pack/InvalidPackArchiveFormatException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * 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 -{ -} diff --git a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php b/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php deleted file mode 100644 index f1608936c..000000000 --- a/app/Exceptions/Service/Pack/UnreadableZipArchiveException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * 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 -{ -} diff --git a/app/Exceptions/Service/Pack/ZipArchiveCreationException.php b/app/Exceptions/Service/Pack/ZipArchiveCreationException.php deleted file mode 100644 index 62b11136d..000000000 --- a/app/Exceptions/Service/Pack/ZipArchiveCreationException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * 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 -{ -} diff --git a/app/Exceptions/Service/Pack/ZipExtractionException.php b/app/Exceptions/Service/Pack/ZipExtractionException.php deleted file mode 100644 index 8a6a82c20..000000000 --- a/app/Exceptions/Service/Pack/ZipExtractionException.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * 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 -{ -} diff --git a/app/Http/Controllers/Admin/Nodes/NodeController.php b/app/Http/Controllers/Admin/Nodes/NodeController.php index 5e285a8c6..5a7bc1e30 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeController.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use Spatie\QueryBuilder\QueryBuilder; use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -39,10 +41,13 @@ class NodeController extends Controller */ public function index(Request $request) { - $nodes = $this->repository - ->setSearchTerm($request->input('query')) - ->getNodeListingData(); + $nodes = QueryBuilder::for( + Node::query()->with('location')->withCount('servers') + ) + ->allowedFilters(['uuid', 'name']) + ->allowedSorts(['id']) + ->paginate(25); - return $this->view->make('admin.nodes.index', compact('nodes')); + return $this->view->make('admin.nodes.index', ['nodes' => $nodes]); } } diff --git a/app/Http/Controllers/Admin/Nodes/NodeViewController.php b/app/Http/Controllers/Admin/Nodes/NodeViewController.php index ba9e2e947..2121985c9 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeViewController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeViewController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; use Illuminate\Http\Request; use Pterodactyl\Models\Node; use Illuminate\Support\Collection; +use Pterodactyl\Models\Allocation; use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -134,7 +135,10 @@ class NodeViewController extends Controller return $this->view->make('admin.nodes.view.allocation', [ 'node' => $node, - 'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id), + 'allocations' => Allocation::query()->where('node_id', $node->id) + ->groupBy('ip') + ->orderByRaw('INET_ATON(ip) ASC') + ->get(['ip']), ]); } diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php deleted file mode 100644 index 98191222e..000000000 --- a/app/Http/Controllers/Admin/PackController.php +++ /dev/null @@ -1,250 +0,0 @@ -. - * - * 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 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->setSearchTerm($request->input('query'))->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); - } -} diff --git a/app/Http/Controllers/Admin/Servers/ServerController.php b/app/Http/Controllers/Admin/Servers/ServerController.php index 31bee2cdc..a0b73f552 100644 --- a/app/Http/Controllers/Admin/Servers/ServerController.php +++ b/app/Http/Controllers/Admin/Servers/ServerController.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use Spatie\QueryBuilder\QueryBuilder; use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\ServerRepository; @@ -42,10 +44,11 @@ class ServerController extends Controller */ public function index(Request $request) { - return $this->view->make('admin.servers.index', [ - 'servers' => $this->repository->setSearchTerm($request->input('query'))->getAllServers( - config()->get('pterodactyl.paginate.admin.servers') - ), - ]); + $servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation')) + ->allowedFilters(['uuid', 'name', 'image']) + ->allowedSorts(['id', 'uuid']) + ->paginate(config()->get('pterodactyl.paginate.admin.servers')); + + return $this->view->make('admin.servers.index', ['servers' => $servers]); } } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index f914013bb..94df5dc21 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -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 diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 18e7e3b43..2afbe1407 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\User; use Prologue\Alerts\AlertsMessageBag; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Translation\Translator; @@ -83,7 +84,10 @@ class UserController extends Controller */ public function index(Request $request) { - $users = $this->repository->setSearchTerm($request->input('query'))->getAllUsersWithCounts(); + $users = QueryBuilder::for(User::query()->withCount('servers')) + ->allowedFilters(['username', 'email', 'uuid']) + ->allowedSorts(['id', 'uuid']) + ->paginate(50); return view('admin.users.index', ['users' => $users]); } @@ -181,11 +185,20 @@ class UserController extends Controller */ public function json(Request $request) { + $users = QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25); + // Handle single user requests. if ($request->query('user_id')) { - return $this->repository->filterById($request->input('user_id')); + $user = User::query()->findOrFail($request->input('user_id')); + $user->md5 = md5(strtolower($user->email)); + + return $user; } - return $this->repository->filterUsersByQuery($request->input('q')); + return $users->map(function ($item) { + $item->md5 = md5(strtolower($item->email)); + + return $item; + }); } } diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index 98ec30574..62ab4ea45 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Locations; use Illuminate\Http\Response; use Pterodactyl\Models\Location; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Locations\LocationUpdateService; use Pterodactyl\Services\Locations\LocationCreationService; use Pterodactyl\Services\Locations\LocationDeletionService; @@ -69,7 +70,10 @@ class LocationController extends ApplicationApiController */ public function index(GetLocationsRequest $request): array { - $locations = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $locations = QueryBuilder::for(Location::query()) + ->allowedFilters(['short', 'long']) + ->allowedSorts(['id']) + ->paginate(100); return $this->fractal->collection($locations) ->transformWith($this->getTransformer(LocationTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index e6b4be6a2..7198611ba 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -3,8 +3,8 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Nodes; use Pterodactyl\Models\Node; -use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Nodes\NodeUpdateService; use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Services\Nodes\NodeDeletionService; @@ -69,7 +69,10 @@ class NodeController extends ApplicationApiController */ public function index(GetNodesRequest $request): array { - $nodes = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $nodes = QueryBuilder::for(Node::query()) + ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) + ->allowedSorts(['id', 'uuid', 'memory', 'disk']) + ->paginate(100); return $this->fractal->collection($nodes) ->transformWith($this->getTransformer(NodeTransformer::class)) @@ -80,11 +83,12 @@ class NodeController extends ApplicationApiController * Return data for a single instance of a node. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest $request + * @param \Pterodactyl\Models\Node $node * @return array */ - public function view(GetNodeRequest $request): array + public function view(GetNodeRequest $request, Node $node): array { - return $this->fractal->item($request->getModel(Node::class)) + return $this->fractal->item($node) ->transformWith($this->getTransformer(NodeTransformer::class)) ->toArray(); } @@ -116,16 +120,15 @@ class NodeController extends ApplicationApiController * Update an existing node on the Panel. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\UpdateNodeRequest $request + * @param \Pterodactyl\Models\Node $node * @return array * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ - public function update(UpdateNodeRequest $request): array + public function update(UpdateNodeRequest $request, Node $node): array { $node = $this->updateService->handle( - $request->getModel(Node::class), $request->validated(), $request->input('reset_secret') === true + $node, $request->validated(), $request->input('reset_secret') === true ); return $this->fractal->item($node) @@ -138,14 +141,15 @@ class NodeController extends ApplicationApiController * currently attached to it. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\DeleteNodeRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function delete(DeleteNodeRequest $request): Response + public function delete(DeleteNodeRequest $request, Node $node): JsonResponse { - $this->deletionService->handle($request->getModel(Node::class)); + $this->deletionService->handle($node); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 69f2706ac..126c91921 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; @@ -59,7 +60,10 @@ class ServerController extends ApplicationApiController */ public function index(GetServersRequest $request): array { - $servers = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $servers = QueryBuilder::for(Server::query()) + ->allowedFilters(['uuid', 'name', 'image', 'external_id']) + ->allowedSorts(['id', 'uuid']) + ->paginate(100); return $this->fractal->collection($servers) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 522cef8f4..c8b309d85 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Users; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; @@ -70,7 +71,10 @@ class UserController extends ApplicationApiController */ public function index(GetUsersRequest $request): array { - $users = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $users = QueryBuilder::for(User::query()) + ->allowedFilters(['email', 'uuid', 'username', 'external_id']) + ->allowedSorts(['id', 'uuid']) + ->paginate(100); return $this->fractal->collection($users) ->transformWith($this->getTransformer(UserTransformer::class)) @@ -82,11 +86,12 @@ class UserController extends ApplicationApiController * were defined in the request. * * @param \Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest $request + * @param \Pterodactyl\Models\User $user * @return array */ - public function view(GetUsersRequest $request): array + public function view(GetUsersRequest $request, User $user): array { - return $this->fractal->item($request->getModel(User::class)) + return $this->fractal->item($user) ->transformWith($this->getTransformer(UserTransformer::class)) ->toArray(); } @@ -146,14 +151,15 @@ class UserController extends ApplicationApiController * on successful deletion. * * @param \Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\User $user + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(DeleteUserRequest $request): Response + public function delete(DeleteUserRequest $request, User $user): JsonResponse { - $this->deletionService->handle($request->getModel(User::class)); + $this->deletionService->handle($user); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Remote/FileDownloadController.php b/app/Http/Controllers/Api/Remote/FileDownloadController.php deleted file mode 100644 index fa4818fc9..000000000 --- a/app/Http/Controllers/Api/Remote/FileDownloadController.php +++ /dev/null @@ -1,50 +0,0 @@ -cache = $cache; - } - - /** - * Handle a request to authenticate a download using a token and return - * the path of the file to the daemon. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - * - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function index(Request $request): JsonResponse - { - $download = $this->cache->pull('Server:Downloads:' . $request->input('token', '')); - - if (is_null($download)) { - throw new NotFoundHttpException('No file was found using the token provided.'); - } - - return response()->json([ - 'path' => array_get($download, 'path'), - 'server' => array_get($download, 'server'), - ]); - } -} diff --git a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php index fc13c8f29..b62d6e5c1 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -14,6 +14,7 @@ use Pterodactyl\Services\Servers\GetUserPermissionsService; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Remote\SftpAuthenticationFormRequest; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; class SftpAuthenticationController extends Controller { @@ -71,11 +72,12 @@ class SftpAuthenticationController extends Controller 'server' => strrev(array_get($parts, 0)), ]; - $this->incrementLoginAttempts($request); if ($this->hasTooManyLoginAttempts($request)) { - return JsonResponse::create([ - 'error' => 'Too many logins attempted too quickly.', - ], JsonResponse::HTTP_TOO_MANY_REQUESTS); + $seconds = $this->limiter()->availableIn($this->throttleKey($request)); + + throw new TooManyRequestsHttpException( + $seconds, "Too many login attempts for this account, please try again in {$seconds} seconds." + ); } /** @var \Pterodactyl\Models\Node $node */ @@ -91,6 +93,8 @@ class SftpAuthenticationController extends Controller $server = $this->serverRepository->getByUuid($connection['server'] ?? ''); if (! password_verify($request->input('password'), $user->password) || $server->node_id !== $node->id) { + $this->incrementLoginAttempts($request); + throw new HttpForbiddenException( 'Authorization credentials were not correct, please try again.' ); diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index 8c10b22d2..505f1a305 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -8,6 +8,7 @@ use Pterodactyl\Models\Server; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AuthenticateServerAccess @@ -64,8 +65,10 @@ class AuthenticateServerAccess } } - if ($server->suspended) { - throw new AccessDeniedHttpException('This server is currently suspended and the functionality requested is unavailable.'); + if ($server->suspended && !$request->routeIs('api:client:server.resources')) { + throw new BadRequestHttpException( + 'This server is currently suspended and the functionality requested is unavailable.' + ); } if (! $server->isInstalled()) { diff --git a/app/Http/Requests/Admin/PackFormRequest.php b/app/Http/Requests/Admin/PackFormRequest.php deleted file mode 100644 index 68b3a8d36..000000000 --- a/app/Http/Requests/Admin/PackFormRequest.php +++ /dev/null @@ -1,49 +0,0 @@ -. - * - * 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; - }); - }); - } -} diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php index 96e0eebe4..6f930615c 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -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; - }); }); } } diff --git a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php index 32f68bd23..15780d695 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -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'), diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php index 532ae1c35..fc367cdaf 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -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(); } diff --git a/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php b/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php index 25dc4f1e1..008b44436 100644 --- a/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Files/GetFileContentsRequest.php @@ -17,7 +17,7 @@ class GetFileContentsRequest extends ClientApiRequest implements ClientPermissio */ public function permission(): string { - return Permission::ACTION_FILE_READ; + return Permission::ACTION_FILE_READ_CONTENT; } /** diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 59517621a..072a74f9b 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -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', ]; diff --git a/app/Models/DaemonKey.php b/app/Models/DaemonKey.php deleted file mode 100644 index fa5bb6a91..000000000 --- a/app/Models/DaemonKey.php +++ /dev/null @@ -1,78 +0,0 @@ - 'integer', - 'server_id' => 'integer', - ]; - - /** - * @var array - */ - protected $dates = [ - self::CREATED_AT, - self::UPDATED_AT, - 'expires_at', - ]; - - /** - * @var array - */ - protected $fillable = ['user_id', 'server_id', 'secret', 'expires_at']; - - /** - * @var array - */ - public static $validationRules = [ - 'user_id' => 'required|numeric|exists:users,id', - 'server_id' => 'required|numeric|exists:servers,id', - 'secret' => 'required|string|min:20', - 'expires_at' => 'required|date', - ]; - - /** - * Return the server relation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function server() - { - return $this->belongsTo(Server::class); - } - - /** - * Return the node relation. - * - * @return \Znck\Eloquent\Relations\BelongsToThrough - * @throws \Exception - */ - public function node() - { - return $this->belongsToThrough(Node::class, Server::class); - } - - /** - * Return the user relation. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function user() - { - return $this->belongsTo(User::class); - } -} diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index d76fed494..016702141 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Models; -use Pterodactyl\Rules\ResolvesToIPAddress; - class DatabaseHost extends Model { /** @@ -60,18 +58,6 @@ class DatabaseHost extends Model 'node_id' => 'sometimes|nullable|integer|exists:nodes,id', ]; - /** - * @return array - */ - public static function getRules() - { - $rules = parent::getRules(); - - $rules['host'] = array_merge($rules['host'], [ new ResolvesToIPAddress() ]); - - return $rules; - } - /** * Gets the node associated with a database host. * diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 2c53c8536..143fe95a8 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -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. * diff --git a/app/Models/Mount.php b/app/Models/Mount.php index ac0b5da9a..023243f93 100644 --- a/app/Models/Mount.php +++ b/app/Models/Mount.php @@ -43,13 +43,8 @@ class Mount extends Model * * @var array */ - protected $attributes = [ + protected $casts = [ 'id' => 'int', - 'uuid' => 'string', - 'name' => 'string', - 'description' => 'string', - 'source' => 'string', - 'target' => 'string', 'read_only' => 'bool', 'user_mountable' => 'bool', ]; @@ -60,7 +55,6 @@ class Mount extends Model * @var string */ public static $validationRules = [ - // 'uuid' => 'required|string|size:36|unique:mounts,uuid', 'name' => 'required|string|min:2|max:64|unique:mounts,name', 'description' => 'nullable|string|max:255', 'source' => 'required|string', diff --git a/app/Models/MountNode.php b/app/Models/MountNode.php new file mode 100644 index 000000000..a897dd6dd --- /dev/null +++ b/app/Models/MountNode.php @@ -0,0 +1,23 @@ +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. * diff --git a/app/Models/Node.php b/app/Models/Node.php index e8a260be8..2f2f6d26f 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -5,7 +5,6 @@ namespace Pterodactyl\Models; use Symfony\Component\Yaml\Yaml; use Illuminate\Container\Container; use Illuminate\Notifications\Notifiable; -use Pterodactyl\Models\Traits\Searchable; use Illuminate\Contracts\Encryption\Encrypter; /** @@ -33,13 +32,13 @@ use Illuminate\Contracts\Encryption\Encrypter; * @property \Carbon\Carbon $updated_at * * @property \Pterodactyl\Models\Location $location + * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations */ class Node extends Model { use Notifiable; - use Searchable; /** * The resource name for this model when it is transformed into an @@ -94,18 +93,6 @@ class Node extends Model 'description', 'maintenance_mode', ]; - /** - * Fields that are searchable. - * - * @var array - */ - protected $searchableColumns = [ - 'name' => 10, - 'fqdn' => 8, - 'location.short' => 4, - 'location.long' => 4, - ]; - /** * @var array */ @@ -182,6 +169,7 @@ class Node extends Model 'bind_port' => $this->daemonSFTP, ], ], + 'allowed_mounts' => $this->mounts->pluck('source')->toArray(), 'remote' => route('index'), ]; } @@ -214,11 +202,19 @@ class Node extends Model */ public function getDecryptedKey(): string { - return (string) Container::getInstance()->make(Encrypter::class)->decrypt( + return (string)Container::getInstance()->make(Encrypter::class)->decrypt( $this->daemon_token ); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough + */ + public function mounts() + { + return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id'); + } + /** * Gets the location associated with a node. * diff --git a/app/Models/Pack.php b/app/Models/Pack.php deleted file mode 100644 index 3846d74eb..000000000 --- a/app/Models/Pack.php +++ /dev/null @@ -1,106 +0,0 @@ - '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); - } -} diff --git a/app/Models/Permission.php b/app/Models/Permission.php index a7eb2709b..f870866e2 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -49,6 +49,7 @@ class Permission extends Model const ACTION_ALLOCATION_DELETE = 'allocation.delete'; const ACTION_FILE_READ = 'file.read'; + const ACTION_FILE_READ_CONTENT = 'file.read-content'; const ACTION_FILE_CREATE = 'file.create'; const ACTION_FILE_UPDATE = 'file.update'; const ACTION_FILE_DELETE = 'file.delete'; @@ -138,7 +139,8 @@ class Permission extends Model 'description' => 'Permissions that control a user\'s ability to modify the filesystem for this server.', 'keys' => [ 'create' => 'Allows a user to create additional files and folders via the Panel or direct upload.', - 'read' => 'Allows a user to view the contents of a directory and read the contents of a file. Users with this permission can also download files.', + 'read' => 'Allows a user to view the contents of a directory, but not view the contents of or download files.', + 'read-content' => 'Allows a user to view the contents of a given file. This will also allow the user to download files.', 'update' => 'Allows a user to update the contents of an existing file or directory.', 'delete' => 'Allows a user to delete files or directories.', 'archive' => 'Allows a user to archive the contents of a directory as well as decompress existing archives on the system.', diff --git a/app/Models/Server.php b/app/Models/Server.php index 91ba9621a..f6de3516c 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Models; use Illuminate\Notifications\Notifiable; -use Pterodactyl\Models\Traits\Searchable; use Illuminate\Database\Query\JoinClause; use Znck\Eloquent\Traits\BelongsToThrough; @@ -28,7 +27,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 +40,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 @@ -50,8 +47,6 @@ use Znck\Eloquent\Traits\BelongsToThrough; * @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule * @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases * @property \Pterodactyl\Models\Location $location - * @property \Pterodactyl\Models\DaemonKey $key - * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys * @property \Pterodactyl\Models\ServerTransfer $transfer * @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts @@ -60,7 +55,6 @@ class Server extends Model { use BelongsToThrough; use Notifiable; - use Searchable; /** * The resource name for this model when it is transformed into an @@ -122,7 +116,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,29 +144,12 @@ class Server extends Model 'allocation_id' => 'integer', 'nest_id' => 'integer', 'egg_id' => 'integer', - 'pack_id' => 'integer', 'installed' => 'integer', 'database_limit' => 'integer', 'allocation_limit' => 'integer', 'backup_limit' => 'integer', ]; - /** - * Parameters for search querying. - * - * @var array - */ - protected $searchableColumns = [ - 'name' => 100, - 'uuid' => 80, - 'uuidShort' => 80, - 'external_id' => 50, - 'user.email' => 40, - 'user.username' => 30, - 'node.name' => 10, - 'pack.name' => 10, - ]; - /** * Returns the format for server allocations when communicating with the Daemon. * @@ -234,16 +210,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. * @@ -326,26 +292,6 @@ class Server extends Model return $this->belongsToThrough(Location::class, Node::class); } - /** - * Return the key belonging to the server owner. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne - */ - public function key() - { - return $this->hasOne(DaemonKey::class, 'user_id', 'owner_id'); - } - - /** - * Returns all of the daemon keys belonging to this server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function keys() - { - return $this->hasMany(DaemonKey::class); - } - /** * Returns the associated server transfer. * @@ -367,10 +313,10 @@ class Server extends Model /** * Returns all mounts that have this server has mounted. * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ public function mounts() { - return $this->belongsToMany(Mount::class); + return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id'); } } diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index d75bbe9ab..ab85b85dd 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -99,14 +99,4 @@ class Subuser extends Model { return $this->hasMany(Permission::class); } - - /** - * Return the key that belongs to this subuser for the server. - * - * @return \Illuminate\Database\Eloquent\Relations\HasOne - */ - public function key() - { - return $this->hasOne(DaemonKey::class, 'server_id', 'server_id')->where('daemon_keys.user_id', '=', $this->user_id); - } } diff --git a/app/Models/Traits/Searchable.php b/app/Models/Traits/Searchable.php deleted file mode 100644 index d1ab0eb86..000000000 --- a/app/Models/Traits/Searchable.php +++ /dev/null @@ -1,13 +0,0 @@ - 100, - 'email' => 100, - 'external_id' => 80, - 'uuid' => 80, - 'name_first' => 40, - 'name_last' => 40, - ]; - /** * Default values for specific fields in the database. * @@ -230,16 +213,6 @@ class User extends Model implements return $this->hasMany(Server::class, 'owner_id'); } - /** - * Return all of the daemon keys that a user belongs to. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function keys() - { - return $this->hasMany(DaemonKey::class); - } - /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index ef8e6cb0a..8a0434f52 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -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; @@ -17,13 +16,11 @@ use Pterodactyl\Repositories\Eloquent\DatabaseRepository; use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\ScheduleRepository; use Pterodactyl\Repositories\Eloquent\SettingsRepository; -use Pterodactyl\Repositories\Eloquent\DaemonKeyRepository; use Pterodactyl\Repositories\Eloquent\AllocationRepository; 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; @@ -36,7 +33,6 @@ use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; @@ -52,7 +48,6 @@ class RepositoryServiceProvider extends ServiceProvider // Eloquent Repositories $this->app->bind(AllocationRepositoryInterface::class, AllocationRepository::class); $this->app->bind(ApiKeyRepositoryInterface::class, ApiKeyRepository::class); - $this->app->bind(DaemonKeyRepositoryInterface::class, DaemonKeyRepository::class); $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(EggRepositoryInterface::class, EggRepository::class); @@ -60,7 +55,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); diff --git a/app/Repositories/Concerns/Searchable.php b/app/Repositories/Concerns/Searchable.php deleted file mode 100644 index 8bb11a552..000000000 --- a/app/Repositories/Concerns/Searchable.php +++ /dev/null @@ -1,64 +0,0 @@ -setSearchTerm($term); - } - - /** - * Set the search term to use when requesting all records from - * the model. - * - * @param string|null $term - * @return $this - */ - public function setSearchTerm(string $term = null) - { - if (empty($term)) { - return $this; - } - - $clone = clone $this; - $clone->searchTerm = $term; - - return $clone; - } - - /** - * Determine if a valid search term is set on this repository. - * - * @return bool - */ - public function hasSearchTerm(): bool - { - return ! empty($this->searchTerm); - } - - /** - * Return the search term. - * - * @return string|null - */ - public function getSearchTerm() - { - return $this->searchTerm; - } -} diff --git a/app/Repositories/Eloquent/AllocationRepository.php b/app/Repositories/Eloquent/AllocationRepository.php index cc7efb23b..15a5db81b 100644 --- a/app/Repositories/Eloquent/AllocationRepository.php +++ b/app/Repositories/Eloquent/AllocationRepository.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Repositories\Eloquent; -use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; @@ -19,20 +18,6 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos return Allocation::class; } - /** - * Return all of the unique IPs that exist for a given node. - * - * @param int $node - * @return \Illuminate\Support\Collection - */ - public function getUniqueAllocationIpsForNode(int $node): Collection - { - return $this->getBuilder()->where('node_id', $node) - ->groupBy('ip') - ->orderByRaw('INET_ATON(ip) ASC') - ->get($this->getColumns()); - } - /** * Return all of the allocations that exist for a node that are not currently * allocated. @@ -42,22 +27,12 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos */ public function getUnassignedAllocationIds(int $node): array { - $results = $this->getBuilder()->select('id')->whereNull('server_id')->where('node_id', $node)->get(); - - return $results->pluck('id')->toArray(); - } - - /** - * Get an array of all allocations that are currently assigned to a given server. - * - * @param int $server - * @return array - */ - public function getAssignedAllocationIds(int $server): array - { - $results = $this->getBuilder()->select('id')->where('server_id', $server)->get(); - - return $results->pluck('id')->toArray(); + return Allocation::query()->select('id') + ->whereNull('server_id') + ->where('node_id', $node) + ->get() + ->pluck('id') + ->toArray(); } /** @@ -71,21 +46,19 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos * @param array $nodes * @return array */ - public function getDiscardableDedicatedAllocations(array $nodes = []): array + protected function getDiscardableDedicatedAllocations(array $nodes = []): array { - $instance = $this->getBuilder()->select( - $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip) as result') - ); + $query = Allocation::query()->selectRaw('CONCAT_WS("-", node_id, ip) as result'); if (! empty($nodes)) { - $instance->whereIn('node_id', $nodes); + $query->whereIn('node_id', $nodes); } - $results = $instance->whereNotNull('server_id') - ->groupBy($this->getBuilder()->raw('CONCAT(node_id, ip)')) - ->get(); - - return $results->pluck('result')->toArray(); + return $query->whereNotNull('server_id') + ->groupByRaw('CONCAT(node_id, ip)') + ->get() + ->pluck('result') + ->toArray(); } /** @@ -98,18 +71,18 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos */ public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false) { - $instance = $this->getBuilder()->whereNull('server_id'); + $query = Allocation::query()->whereNull('server_id'); if (! empty($nodes)) { - $instance->whereIn('node_id', $nodes); + $query->whereIn('node_id', $nodes); } if (! empty($ports)) { - $instance->where(function (Builder $query) use ($ports) { + $query->where(function (Builder $inner) use ($ports) { $whereIn = []; foreach ($ports as $port) { if (is_array($port)) { - $query->orWhereBetween('port', $port); + $inner->orWhereBetween('port', $port); continue; } @@ -117,7 +90,7 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos } if (! empty($whereIn)) { - $query->orWhereIn('port', $whereIn); + $inner->orWhereIn('port', $whereIn); } }); } @@ -128,12 +101,12 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos $discard = $this->getDiscardableDedicatedAllocations($nodes); if (! empty($discard)) { - $instance->whereNotIn( + $query->whereNotIn( $this->getBuilder()->raw('CONCAT_WS("-", node_id, ip)'), $discard ); } } - return $instance->inRandomOrder()->first(); + return $query->inRandomOrder()->first(); } } diff --git a/app/Repositories/Eloquent/DaemonKeyRepository.php b/app/Repositories/Eloquent/DaemonKeyRepository.php deleted file mode 100644 index 2f11ff962..000000000 --- a/app/Repositories/Eloquent/DaemonKeyRepository.php +++ /dev/null @@ -1,87 +0,0 @@ -relationLoaded('server') || $refresh) { - $key->load('server'); - } - - if (! $key->relationLoaded('user') || $refresh) { - $key->load('user'); - } - - return $key; - } - - /** - * Return a daemon key with the associated server relation attached. - * - * @param string $key - * @return \Pterodactyl\Models\DaemonKey - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function getKeyWithServer(string $key): DaemonKey - { - Assert::notEmpty($key, 'Expected non-empty string as first argument passed to ' . __METHOD__); - - try { - return $this->getBuilder()->with('server')->where('secret', '=', $key)->firstOrFail($this->getColumns()); - } catch (ModelNotFoundException $exception) { - throw new RecordNotFoundException; - } - } - - /** - * Get all of the keys for a specific user including the information needed - * from their server relation for revocation on the daemon. - * - * @param \Pterodactyl\Models\User $user - * @return \Illuminate\Support\Collection - */ - public function getKeysForRevocation(User $user): Collection - { - return $this->getBuilder()->with('node')->where('user_id', $user->id)->get($this->getColumns()); - } - - /** - * Delete an array of daemon keys from the database. Used primarily in - * conjunction with getKeysForRevocation. - * - * @param array $ids - * @return bool|int - */ - public function deleteKeys(array $ids) - { - return $this->getBuilder()->whereIn('id', $ids)->delete(); - } -} diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index 34b5247af..bd8133022 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -289,12 +289,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf */ public function all(): Collection { - $instance = $this->getBuilder(); - if (is_subclass_of(get_called_class(), SearchableInterface::class) && $this->hasSearchTerm()) { - $instance = $instance->search($this->getSearchTerm()); - } - - return $instance->get($this->getColumns()); + return $this->getBuilder()->get($this->getColumns()); } /** @@ -305,12 +300,7 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf */ public function paginated(int $perPage): LengthAwarePaginator { - $instance = $this->getBuilder(); - if (is_subclass_of(get_called_class(), SearchableInterface::class) && $this->hasSearchTerm()) { - $instance = $instance->search($this->getSearchTerm()); - } - - return $instance->paginate($perPage, $this->getColumns()); + return $this->getBuilder()->paginate($perPage, $this->getColumns()); } /** diff --git a/app/Repositories/Eloquent/LocationRepository.php b/app/Repositories/Eloquent/LocationRepository.php index 47d5e321a..6d14d5aa6 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -4,15 +4,12 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Location; use Illuminate\Support\Collection; -use Pterodactyl\Repositories\Concerns\Searchable; use Illuminate\Database\Eloquent\ModelNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; class LocationRepository extends EloquentRepository implements LocationRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * diff --git a/app/Repositories/Eloquent/MountRepository.php b/app/Repositories/Eloquent/MountRepository.php index c75ba2aa6..286e07913 100644 --- a/app/Repositories/Eloquent/MountRepository.php +++ b/app/Repositories/Eloquent/MountRepository.php @@ -5,14 +5,11 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Mount; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; -use Pterodactyl\Repositories\Concerns\Searchable; use Illuminate\Database\Eloquent\ModelNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; class MountRepository extends EloquentRepository { - use Searchable; - /** * Return the model backing this repository. * diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php index 9c0fcf73c..96b3df36f 100644 --- a/app/Repositories/Eloquent/NestRepository.php +++ b/app/Repositories/Eloquent/NestRepository.php @@ -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()); diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 2385c9109..b7463001e 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -5,14 +5,11 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Node; use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; -use Pterodactyl\Repositories\Concerns\Searchable; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; class NodeRepository extends EloquentRepository implements NodeRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * @@ -85,22 +82,6 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa })->toArray(); } - /** - * Return all available nodes with a searchable interface. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getNodeListingData(): LengthAwarePaginator - { - $instance = $this->getBuilder()->with('location')->withCount('servers'); - - if ($this->hasSearchTerm()) { - $instance->search($this->getSearchTerm()); - } - - return $instance->paginate(25, $this->getColumns()); - } - /** * Return a single node with location and server information. * diff --git a/app/Repositories/Eloquent/PackRepository.php b/app/Repositories/Eloquent/PackRepository.php deleted file mode 100644 index bdd5eca6a..000000000 --- a/app/Repositories/Eloquent/PackRepository.php +++ /dev/null @@ -1,53 +0,0 @@ -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') - ->search($this->getSearchTerm()) - ->paginate(50, $this->getColumns()); - } -} diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index f749d0d18..5e884a661 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -5,7 +5,6 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\Server; use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Builder; -use Pterodactyl\Repositories\Concerns\Searchable; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; @@ -13,8 +12,6 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class ServerRepository extends EloquentRepository implements ServerRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * @@ -25,19 +22,6 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt return Server::class; } - /** - * Returns a listing of all servers that exist including relationships. - * - * @param int $paginate - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllServers(int $paginate): LengthAwarePaginator - { - $instance = $this->getBuilder()->with('node', 'user', 'allocation')->search($this->getSearchTerm()); - - return $instance->paginate($paginate, $this->getColumns()); - } - /** * Load the egg relations onto the server model. * @@ -63,11 +47,11 @@ 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); - } elseif (is_null($server) && ! is_null($node)) { + } else if (is_null($server) && ! is_null($node)) { $instance = $instance->where('node_id', '=', $node); } @@ -83,11 +67,11 @@ 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); - } elseif (is_null($server) && ! is_null($node)) { + } else if (is_null($server) && ! is_null($node)) { $instance = $instance->where('node_id', '=', $node); } @@ -140,7 +124,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 +151,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 +164,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, ]; } @@ -229,9 +208,9 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt if (! empty($nodes) && ! empty($servers)) { $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); - } elseif (empty($nodes) && ! empty($servers)) { + } else if (empty($nodes) && ! empty($servers)) { $instance->whereIn('id', $servers); - } elseif (! empty($nodes) && empty($servers)) { + } else if (! empty($nodes) && empty($servers)) { $instance->whereIn('node_id', $nodes); } diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php index b6468b5bb..72a88efb0 100644 --- a/app/Repositories/Eloquent/UserRepository.php +++ b/app/Repositories/Eloquent/UserRepository.php @@ -3,15 +3,10 @@ namespace Pterodactyl\Repositories\Eloquent; use Pterodactyl\Models\User; -use Illuminate\Support\Collection; -use Pterodactyl\Repositories\Concerns\Searchable; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; class UserRepository extends EloquentRepository implements UserRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * @@ -21,55 +16,4 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa { return User::class; } - - /** - * Return all users with counts of servers and subusers of servers. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllUsersWithCounts(): LengthAwarePaginator - { - return $this->getBuilder()->withCount('servers') - ->search($this->getSearchTerm()) - ->paginate(50, $this->getColumns()); - } - - /** - * Return all matching models for a user in a format that can be used for dropdowns. - * - * @param string|null $query - * @return \Illuminate\Support\Collection - */ - public function filterUsersByQuery(?string $query): Collection - { - $this->setColumns([ - 'id', 'email', 'username', 'name_first', 'name_last', - ]); - - $instance = $this->getBuilder()->search($query)->get($this->getColumns()); - - return $instance->transform(function ($item) { - $item->md5 = md5(strtolower($item->email)); - - return $item; - }); - } - - /** - * Returns a user with the given id in a format that can be used for dropdowns. - * - * @param int $id - * @return \Pterodactyl\Models\Model - */ - public function filterById(int $id): \Pterodactyl\Models\Model - { - $this->setColumns([ - 'id', 'email', 'username', 'name_first', 'name_last', - ]); - - $model = $this->getBuilder()->findOrFail($id, $this->getColumns())->getModel(); - $model->md5 = md5(strtolower($model->email)); - - return $model; - } } diff --git a/app/Rules/ResolvesToIPAddress.php b/app/Rules/ResolvesToIPAddress.php deleted file mode 100644 index e1421b52c..000000000 --- a/app/Rules/ResolvesToIPAddress.php +++ /dev/null @@ -1,49 +0,0 @@ -url($resource) . '" - rel="stylesheet preload" - as="style" - crossorigin="anonymous" - integrity="' . $this->integrity($resource) . '" - referrerpolicy="no-referrer">'; + $attributes = [ + 'href' => $this->url($resource), + 'rel' => 'stylesheet preload', + 'as' => 'style', + 'crossorigin' => 'anonymous', + 'referrerpolicy' => 'no-referrer', + ]; + + if (config('pterodactyl.assets.use_hash')) { + $attributes['integrity'] = $this->integrity($resource); + } + + $output = ' $value) { + $output .= " $key=\"$value\""; + } + + return $output . '>'; } /** @@ -100,9 +112,21 @@ class AssetHashService */ public function js(string $resource): string { - return ''; + $attributes = [ + 'src' => $this->url($resource), + 'crossorigin' => 'anonymous', + ]; + + if (config('pterodactyl.assets.use_hash')) { + $attributes['integrity'] = $this->integrity($resource); + } + + $output = ' $value) { + $output .= " $key=\"$value\""; + } + + return $output . '>'; } /** diff --git a/app/Services/Packs/ExportPackService.php b/app/Services/Packs/ExportPackService.php deleted file mode 100644 index 1403bfdca..000000000 --- a/app/Services/Packs/ExportPackService.php +++ /dev/null @@ -1,97 +0,0 @@ -. - * - * 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; - } -} diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php deleted file mode 100644 index 5179ea0dd..000000000 --- a/app/Services/Packs/PackCreationService.php +++ /dev/null @@ -1,104 +0,0 @@ -. - * - * 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; - } -} diff --git a/app/Services/Packs/PackDeletionService.php b/app/Services/Packs/PackDeletionService.php deleted file mode 100644 index cd9be47b6..000000000 --- a/app/Services/Packs/PackDeletionService.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * 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(); - } -} diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php deleted file mode 100644 index 52c32818c..000000000 --- a/app/Services/Packs/PackUpdateService.php +++ /dev/null @@ -1,75 +0,0 @@ -. - * - * 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); - } -} diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php deleted file mode 100644 index dbefd89a1..000000000 --- a/app/Services/Packs/TemplateUploadService.php +++ /dev/null @@ -1,125 +0,0 @@ -. - * - * 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; - } -} diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index b6768fdb1..86ff5bb69 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -159,7 +159,7 @@ class BuildModificationService // Handle removal of allocations from this server. if (array_key_exists('remove_allocations', $data) && ! empty($data['remove_allocations'])) { - $assigned = $this->allocationRepository->getAssignedAllocationIds($server->id); + $assigned = $server->allocations->pluck('id')->toArray(); $updateIds = []; foreach ($data['remove_allocations'] as $allocation) { diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php index fea2eaac0..f17405fad 100644 --- a/app/Services/Servers/ServerConfigurationStructureService.php +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -9,12 +9,13 @@ namespace Pterodactyl\Services\Servers; +use Pterodactyl\Models\Mount; use Pterodactyl\Models\Server; 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 @@ -66,27 +67,15 @@ class ServerConfigurationStructureService * * @param \Pterodactyl\Models\Server $server * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ protected function returnCurrentFormat(Server $server) { - $mounts = $server->mounts; - foreach ($mounts as $mount) { - unset($mount->id); - unset($mount->uuid); - unset($mount->name); - unset($mount->description); - $mount->read_only = $mount->read_only == 1; - unset($mount->user_mountable); - unset($mount->pivot); - } - return [ 'uuid' => $server->uuid, 'suspended' => (bool) $server->suspended, 'environment' => $this->environment->handle($server), 'invocation' => $server->startup, + 'skip_egg_scripts' => $server->skip_scripts, 'build' => [ 'memory_limit' => $server->memory, 'swap' => $server->swap, @@ -95,11 +84,6 @@ class ServerConfigurationStructureService 'threads' => $server->threads, 'disk_space' => $server->disk, ], - 'service' => [ - 'egg' => $server->egg->uuid, - 'pack' => $server->pack ? $server->pack->uuid : null, - 'skip_scripts' => $server->skip_scripts, - ], 'container' => [ 'image' => $server->image, 'oom_disabled' => $server->oom_disabled, @@ -112,7 +96,13 @@ class ServerConfigurationStructureService ], 'mappings' => $server->getAllocationMappings(), ], - 'mounts' => $mounts, + 'mounts' => $server->mounts->map(function (Mount $mount) { + return [ + 'source' => $mount->source, + 'target' => $mount->target, + 'read_only' => $mount->read_only, + ]; + }), ]; } @@ -149,7 +139,6 @@ class ServerConfigurationStructureService ], 'service' => [ 'egg' => $server->egg->uuid, - 'pack' => $server->pack ? $server->pack->uuid : null, 'skip_scripts' => $server->skip_scripts, ], 'rebuild' => false, diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 6d70d23e4..589a790e9 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -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, diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index 405fee47b..7bec8fac6 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -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), ]); diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/Commands/EnvironmentWriterTrait.php index bc4d2486e..6726e931f 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -30,7 +30,10 @@ trait EnvironmentWriterTrait $saveContents = file_get_contents($path); collect($values)->each(function ($value, $key) use (&$saveContents) { $key = strtoupper($key); - if (str_contains($value, ' ') && ! preg_match('/\"(.*)\"/', $value)) { + // If the key value is not sorrounded by quotation marks, and contains anything that could reasonably + // cause environment parsing issues, wrap it in quotes before writing it. This also adds slashes to the + // value to ensure quotes within it don't cause us issues. + if (! preg_match('/^\"(.*)\"$/', $value) && preg_match('/([^\w.\-+\/])+/', $value)) { $value = sprintf('"%s"', addslashes($value)); } diff --git a/app/Transformers/Api/Application/LocationTransformer.php b/app/Transformers/Api/Application/LocationTransformer.php index d54e77d20..7d24cc974 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -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 diff --git a/app/Transformers/Api/Application/PackTransformer.php b/app/Transformers/Api/Application/PackTransformer.php deleted file mode 100644 index e77bdd459..000000000 --- a/app/Transformers/Api/Application/PackTransformer.php +++ /dev/null @@ -1,40 +0,0 @@ - $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), - ]; - } -} diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index d9df4263a..e2c32eb76 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -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 diff --git a/app/Transformers/Daemon/ApiKeyTransformer.php b/app/Transformers/Daemon/ApiKeyTransformer.php deleted file mode 100644 index 292cd6fcf..000000000 --- a/app/Transformers/Daemon/ApiKeyTransformer.php +++ /dev/null @@ -1,76 +0,0 @@ -repository = $repository; - $this->keyRepository = $keyRepository; - } - - /** - * Return a listing of servers that a daemon key can access. - * - * @param \Pterodactyl\Models\DaemonKey $key - * @return array - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function transform(DaemonKey $key) - { - $this->keyRepository->loadServerAndUserRelations($key); - - if ($key->user_id === $key->getRelation('server')->owner_id || $key->getRelation('user')->root_admin) { - return [ - 'id' => $key->getRelation('server')->uuid, - 'is_temporary' => true, - 'expires_in' => max(Carbon::now()->diffInSeconds($key->expires_at, false), 0), - 'permissions' => ['s:*'], - ]; - } - - $subuser = $this->repository->getWithPermissionsUsingUserAndServer($key->user_id, $key->server_id); - - $permissions = $subuser->getRelation('permissions')->pluck('permission')->toArray(); - $mappings = Permission::getPermissions(true); - $daemonPermissions = ['s:console']; - - foreach ($permissions as $permission) { - if (! is_null(array_get($mappings, $permission))) { - $daemonPermissions[] = array_get($mappings, $permission); - } - } - - return [ - 'id' => $key->getRelation('server')->uuid, - 'is_temporary' => true, - 'expires_in' => max(Carbon::now()->diffInSeconds($key->expires_at, false), 0), - 'permissions' => $daemonPermissions, - ]; - } -} diff --git a/app/Transformers/Daemon/FileObjectTransformer.php b/app/Transformers/Daemon/FileObjectTransformer.php index acaba0c43..84fcaf2d4 100644 --- a/app/Transformers/Daemon/FileObjectTransformer.php +++ b/app/Transformers/Daemon/FileObjectTransformer.php @@ -14,14 +14,6 @@ class FileObjectTransformer extends BaseDaemonTransformer */ private $editable = []; - /** - * FileObjectTransformer constructor. - */ - public function __construct() - { - $this->editable = config('pterodactyl.files.editable', []); - } - /** * Transform a file object response from the daemon into a standardized response. * @@ -36,8 +28,7 @@ class FileObjectTransformer extends BaseDaemonTransformer 'size' => Arr::get($item, 'size'), 'is_file' => Arr::get($item, 'file', true), 'is_symlink' => Arr::get($item, 'symlink', false), - 'is_editable' => in_array(Arr::get($item, 'mime', ''), $this->editable), - 'mimetype' => Arr::get($item, 'mime'), + 'mimetype' => Arr::get($item, 'mime', 'application/octet-stream'), 'created_at' => Carbon::parse(Arr::get($item, 'created', ''))->toIso8601String(), 'modified_at' => Carbon::parse(Arr::get($item, 'modified', ''))->toIso8601String(), ]; diff --git a/config/database.php b/config/database.php index 63b0b9cb6..32fca7581 100644 --- a/config/database.php +++ b/config/database.php @@ -96,6 +96,8 @@ return [ 'client' => 'predis', 'default' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), @@ -103,6 +105,8 @@ return [ ], 'sessions' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'host' => env('REDIS_HOST', 'localhost'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 671a64fd3..aba0d729a 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -10,7 +10,7 @@ return [ | setup on the panel. When set to true, configurations stored in the | database will not be applied. */ - 'load_environment_only' => (bool) env('APP_ENVIRONMENT_ONLY', false), + 'load_environment_only' => (bool)env('APP_ENVIRONMENT_ONLY', false), /* |-------------------------------------------------------------------------- @@ -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), @@ -102,29 +101,6 @@ return [ 'high' => env('QUEUE_HIGH', 'high'), ], - /* - |-------------------------------------------------------------------------- - | Console Configuration - |-------------------------------------------------------------------------- - | - | Configure the speed at which data is rendered to the console. - */ - 'console' => [ - 'count' => env('CONSOLE_PUSH_COUNT', 10), - 'frequency' => env('CONSOLE_PUSH_FREQ', 200), - ], - - /* - |-------------------------------------------------------------------------- - | Daemon Connection Details - |-------------------------------------------------------------------------- - | - | Configuration for support of the new Golang based daemon. - */ - 'daemon' => [ - 'use_new_daemon' => (bool) env('APP_USE_NEW_DAEMON', false), - ], - /* |-------------------------------------------------------------------------- | Task Timers @@ -178,20 +154,6 @@ return [ */ 'files' => [ 'max_edit_size' => env('PTERODACTYL_FILES_MAX_EDIT_SIZE', 1024 * 1024 * 4), - 'editable' => [ - 'application/json', - 'application/javascript', - 'application/xml', - 'application/xhtml+xml', - 'inode/x-empty', - 'text/xml', - 'text/css', - 'text/html', - 'text/plain', - 'text/x-perl', - 'text/x-shellscript', - 'text/x-python', - ], ], /* @@ -226,4 +188,15 @@ return [ 'environment_variables' => [ 'P_SERVER_ALLOCATION_LIMIT' => 'allocation_limit', ], + + /* + |-------------------------------------------------------------------------- + | Asset Verification + |-------------------------------------------------------------------------- + | + | This section controls the output format for JS & CSS assets. + */ + 'assets' => [ + 'use_hash' => env('PTERODACTYL_USE_ASSET_HASH', false), + ], ]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 6d6208f4f..0525c30be 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -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 []; }); @@ -194,13 +181,6 @@ $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { ]; }); -$factory->define(Pterodactyl\Models\DaemonKey::class, function (Faker $faker) { - return [ - 'secret' => 'i_' . str_random(40), - 'expires_at' => \Carbon\Carbon::now()->addMinutes(10)->toDateTimeString(), - ]; -}); - $factory->define(Pterodactyl\Models\ApiKey::class, function (Faker $faker) { static $token; diff --git a/database/migrations/2020_04_03_230614_create_backups_table.php b/database/migrations/2020_04_03_230614_create_backups_table.php index ead68105c..68eeee2ce 100644 --- a/database/migrations/2020_04_03_230614_create_backups_table.php +++ b/database/migrations/2020_04_03_230614_create_backups_table.php @@ -1,5 +1,6 @@ TABLE_NAME, $result->TABLE_NAME. '_plugin_bak'); + } + Schema::create('backups', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedInteger('server_id'); diff --git a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php index 689da89b6..92121b7df 100644 --- a/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php +++ b/database/migrations/2020_04_26_111208_add_backup_limit_to_servers.php @@ -1,5 +1,6 @@ unsignedInteger('backup_limit')->default(0)->after('database_limit'); - }); + $db = config('database.default'); + // Same as in the backups migration, we need to handle that plugin messing with the data structure + // here. If we find a result we'll actually keep the column around since we can maintain that backup + // limit, but we need to correct the column definition a bit. + $results = DB::select('SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = \'servers\' AND COLUMN_NAME = \'backup_limit\'', [ + config("database.connections.{$db}.database") + ]); + + if (count($results) === 1) { + Schema::table('servers', function (Blueprint $table) { + $table->unsignedInteger('backup_limit')->default(0)->change(); + }); + } else { + Schema::table('servers', function (Blueprint $table) { + $table->unsignedInteger('backup_limit')->default(0)->after('database_limit'); + }); + } } /** diff --git a/database/migrations/2020_09_13_110007_drop_packs_from_servers.php b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php new file mode 100644 index 000000000..b1d2c1bf1 --- /dev/null +++ b/database/migrations/2020_09_13_110007_drop_packs_from_servers.php @@ -0,0 +1,34 @@ +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'); + }); + } +} diff --git a/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php new file mode 100644 index 000000000..879a64bdf --- /dev/null +++ b/database/migrations/2020_09_13_110021_drop_packs_from_api_key_permissions.php @@ -0,0 +1,32 @@ +dropColumn('r_packs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->unsignedTinyInteger('r_packs')->default(0); + }); + } +} diff --git a/database/migrations/2020_09_13_110047_drop_packs_table.php b/database/migrations/2020_09_13_110047_drop_packs_table.php new file mode 100644 index 000000000..2ab85a5de --- /dev/null +++ b/database/migrations/2020_09_13_110047_drop_packs_table.php @@ -0,0 +1,43 @@ +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(); + }); + } +} diff --git a/database/migrations/2020_09_13_113503_drop_daemon_key_table.php b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php new file mode 100644 index 000000000..e418a85e9 --- /dev/null +++ b/database/migrations/2020_09_13_113503_drop_daemon_key_table.php @@ -0,0 +1,40 @@ +increments('id'); + $table->unsignedInteger('server_id'); + $table->unsignedInteger('user_id'); + $table->string('secret')->unique(); + $table->timestamp('expires_at'); + $table->timestamps(); + }); + + Schema::table('daemon_keys', function (Blueprint $table) { + $table->foreign('server_id')->references('id')->on('servers')->cascadeOnDelete(); + $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); + }); + } +} diff --git a/package.json b/package.json index e939af813..15e96c4bd 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,8 @@ "@fortawesome/free-solid-svg-icons": "^5.9.0", "@fortawesome/react-fontawesome": "0.1.4", "axios": "^0.19.2", - "ayu-ace": "^2.0.4", - "brace": "^0.11.1", "chart.js": "^2.8.0", + "codemirror": "^5.57.0", "date-fns": "^2.14.0", "debounce": "^1.2.0", "deepmerge": "^4.2.2", @@ -57,6 +56,7 @@ "@babel/preset-typescript": "^7.7.4", "@babel/runtime": "^7.7.5", "@types/chart.js": "^2.8.5", + "@types/codemirror": "^0.0.98", "@types/debounce": "^1.2.0", "@types/events": "^3.0.0", "@types/node": "^12.6.9", diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 3e0718a46..7416451da 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -161,7 +161,7 @@ function initUserIdSelect(data) { data: function (params) { return { - q: params.term, // search term + filter: { email: params.term }, page: params.page, }; }, diff --git a/resources/lang/en/admin/pack.php b/resources/lang/en/admin/pack.php deleted file mode 100644 index e3a175f62..000000000 --- a/resources/lang/en/admin/pack.php +++ /dev/null @@ -1,16 +0,0 @@ -. - * - * 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.', - ], -]; diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php index f1d58c6c6..4d44c4ff9 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -4,7 +4,7 @@ return [ 'daemon_connection_failed' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.', 'node' => [ 'servers_attached' => 'A node must have no servers linked to it in order to be deleted.', - 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (core.json) for the daemon to apply these changes.', + 'daemon_off_config_updated' => 'The daemon configuration has been updated, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (config.yml) for the daemon to apply these changes.', ], 'allocations' => [ 'server_using' => 'A server is currently assigned to this allocation. An allocation can only be deleted if no server is currently assigned.', @@ -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.', diff --git a/resources/scripts/api/server/files/loadDirectory.ts b/resources/scripts/api/server/files/loadDirectory.ts index 77e44bce8..1caf6bf9d 100644 --- a/resources/scripts/api/server/files/loadDirectory.ts +++ b/resources/scripts/api/server/files/loadDirectory.ts @@ -8,11 +8,11 @@ export interface FileObject { size: number; isFile: boolean; isSymlink: boolean; - isEditable: boolean; mimetype: string; createdAt: Date; modifiedAt: Date; isArchiveType: () => boolean; + isEditable: () => boolean; } export default async (uuid: string, directory?: string): Promise => { diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts index 595f2b9c8..f17787e03 100644 --- a/resources/scripts/api/transformers.ts +++ b/resources/scripts/api/transformers.ts @@ -19,7 +19,6 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ size: Number(data.attributes.size), isFile: data.attributes.is_file, isSymlink: data.attributes.is_symlink, - isEditable: data.attributes.is_editable, mimetype: data.attributes.mimetype, createdAt: new Date(data.attributes.created_at), modifiedAt: new Date(data.attributes.modified_at), @@ -39,6 +38,19 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ 'application/zip', // .zip ].indexOf(this.mimetype) >= 0; }, + + isEditable: function () { + if (this.isArchiveType() || !this.isFile) return false; + + const matches = [ + 'application/jar', + 'application/octet-stream', + 'inode/directory', + /^image\//, + ]; + + return matches.every(m => !this.mimetype.match(m)); + }, }); export const rawDataToServerBackup = ({ attributes }: FractalResponseData): ServerBackup => ({ diff --git a/resources/scripts/components/NavigationBar.tsx b/resources/scripts/components/NavigationBar.tsx index 4f122a82d..384a32edc 100644 --- a/resources/scripts/components/NavigationBar.tsx +++ b/resources/scripts/components/NavigationBar.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components/macro'; import * as config from '@/../../tailwind.config.js'; const Navigation = styled.div` - ${tw`w-full bg-neutral-900 shadow-md`}; + ${tw`w-full bg-neutral-900 shadow-md overflow-x-auto`}; & > div { ${tw`mx-auto w-full flex items-center`}; diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index 304fe5630..33f0eb561 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { Helmet } from 'react-helmet'; import ContentBox from '@/components/elements/ContentBox'; import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm'; import getApiKeys, { ApiKey } from '@/api/account/getApiKeys'; @@ -8,21 +7,38 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; import deleteApiKey from '@/api/account/deleteApiKey'; -import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; +import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import FlashMessageRender from '@/components/FlashMessageRender'; import { httpErrorToHuman } from '@/api/http'; import { format } from 'date-fns'; import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; +import { breakpoint } from '@/theme'; +import styled from 'styled-components/macro'; import GreyRowBox from '@/components/elements/GreyRowBox'; +const Container = styled.div` + ${tw`flex flex-wrap my-10`}; + + & > div { + ${tw`w-full`}; + + ${breakpoint('md')` + width: calc(50% - 1rem); + `} + + ${breakpoint('xl')` + ${tw`w-auto flex-1`}; + `} + } +`; + export default () => { const [ deleteIdentifier, setDeleteIdentifier ] = useState(''); const [ keys, setKeys ] = useState([]); const [ loading, setLoading ] = useState(true); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); - const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); useEffect(() => { clearFlashes('account'); @@ -50,16 +66,13 @@ export default () => { }; return ( - - - {name} | API - + -
- + + setKeys(s => ([ ...s!, key ]))}/> - + { )) } -
+
); }; diff --git a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx index d495400b4..98e7a8d53 100644 --- a/resources/scripts/components/dashboard/AccountOverviewContainer.tsx +++ b/resources/scripts/components/dashboard/AccountOverviewContainer.tsx @@ -1,6 +1,4 @@ import * as React from 'react'; -import { Helmet } from 'react-helmet'; -import { ApplicationStore } from '@/state'; import ContentBox from '@/components/elements/ContentBox'; import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm'; import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm'; @@ -9,7 +7,6 @@ import PageContentBlock from '@/components/elements/PageContentBlock'; import tw from 'twin.macro'; import { breakpoint } from '@/theme'; import styled from 'styled-components/macro'; -import { useStoreState } from 'easy-peasy'; const Container = styled.div` ${tw`flex flex-wrap my-10`}; @@ -28,12 +25,8 @@ const Container = styled.div` `; export default () => { - const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); return ( - - - {name} | Account Overview - + diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 1e1e702ca..f8b13eda2 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,7 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Helmet } from 'react-helmet'; import { Server } from '@/api/server/getServer'; -import { ApplicationStore } from '@/state'; import getServers from '@/api/getServers'; import ServerRow from '@/components/dashboard/ServerRow'; import Spinner from '@/components/elements/Spinner'; @@ -20,7 +18,6 @@ export default () => { const [ page, setPage ] = useState(1); const { rootAdmin } = useStoreState(state => state.user.data!); const [ showOnlyAdmin, setShowOnlyAdmin ] = usePersistedState('show_all_servers', false); - const name = useStoreState((state: ApplicationStore) => state.settings.data!.name); const { data: servers, error } = useSWR>( [ '/api/client/servers', showOnlyAdmin, page ], @@ -33,10 +30,7 @@ export default () => { }, [ error ]); return ( - - - {name} | Dashboard - + {rootAdmin &&

diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index a504f1951..559932714 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -55,24 +55,17 @@ export default ({ server, className }: { server: Server; className?: string }) = return ( -

+
-
-
-
-

{server.name}

-
+
+

{server.name}

+ {!!server.description && +

{server.description}

+ }
-
-
+ -
+
{!stats ? !statsError ? @@ -102,7 +95,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
: -
+ -
+

of {memorylimit}

-
+

of {disklimit}

+
+
+
+
+
}
diff --git a/resources/scripts/components/elements/AceEditor.tsx b/resources/scripts/components/elements/AceEditor.tsx deleted file mode 100644 index 47fba4edb..000000000 --- a/resources/scripts/components/elements/AceEditor.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import ace, { Editor } from 'brace'; -import styled from 'styled-components/macro'; -import tw from 'twin.macro'; -import modes from '@/modes'; - -// @ts-ignore -require('brace/ext/modelist'); -require('ayu-ace/mirage'); - -const EditorContainer = styled.div` - min-height: 16rem; - height: calc(100vh - 20rem); - ${tw`relative`}; - - #editor { - ${tw`rounded h-full`}; - } -`; - -Object.keys(modes).forEach(mode => require(`brace/mode/${mode}`)); -const modelist = ace.acequire('ace/ext/modelist'); - -export interface Props { - style?: React.CSSProperties; - initialContent?: string; - mode: string; - filename?: string; - onModeChanged: (mode: string) => void; - fetchContent: (callback: () => Promise) => void; - onContentSaved: () => void; -} - -export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { - const [ editor, setEditor ] = useState(); - const ref = useCallback(node => { - if (node) setEditor(ace.edit('editor')); - }, []); - - useEffect(() => { - if (modelist && filename) { - onModeChanged(modelist.getModeForPath(filename).mode.replace(/^ace\/mode\//, '')); - } - }, [ filename ]); - - useEffect(() => { - editor && editor.session.setMode(`ace/mode/${mode}`); - }, [ editor, mode ]); - - useEffect(() => { - editor && editor.session.setValue(initialContent || ''); - }, [ editor, initialContent ]); - - useEffect(() => { - if (!editor) { - fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); - return; - } - - editor.setTheme('ace/theme/ayu-mirage'); - - editor.$blockScrolling = Infinity; - editor.container.style.lineHeight = '1.375rem'; - editor.container.style.fontWeight = '500'; - editor.renderer.updateFontSize(); - editor.renderer.setShowPrintMargin(false); - editor.session.setTabSize(4); - editor.session.setUseSoftTabs(true); - - editor.commands.addCommand({ - name: 'Save', - bindKey: { win: 'Ctrl-s', mac: 'Command-s' }, - exec: () => onContentSaved(), - }); - - fetchContent(() => Promise.resolve(editor.session.getValue())); - }, [ editor, fetchContent, onContentSaved ]); - - return ( - -
- - ); -}; diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx new file mode 100644 index 000000000..50b5449c5 --- /dev/null +++ b/resources/scripts/components/elements/CodemirrorEditor.tsx @@ -0,0 +1,217 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import CodeMirror from 'codemirror'; +import styled from 'styled-components/macro'; +import tw from 'twin.macro'; +import modes, { Mode } from '@/modes'; + +require('codemirror/lib/codemirror.css'); +require('codemirror/theme/ayu-mirage.css'); +require('codemirror/addon/edit/closebrackets'); +require('codemirror/addon/edit/closetag'); +require('codemirror/addon/edit/matchbrackets'); +require('codemirror/addon/edit/matchtags'); +require('codemirror/addon/edit/trailingspace'); +require('codemirror/addon/fold/foldcode'); +require('codemirror/addon/fold/foldgutter.css'); +require('codemirror/addon/fold/foldgutter'); +require('codemirror/addon/fold/brace-fold'); +require('codemirror/addon/fold/comment-fold'); +require('codemirror/addon/fold/indent-fold'); +require('codemirror/addon/fold/markdown-fold'); +require('codemirror/addon/fold/xml-fold'); +require('codemirror/addon/hint/css-hint'); +require('codemirror/addon/hint/html-hint'); +require('codemirror/addon/hint/javascript-hint'); +require('codemirror/addon/hint/show-hint.css'); +require('codemirror/addon/hint/show-hint'); +require('codemirror/addon/hint/sql-hint'); +require('codemirror/addon/hint/xml-hint'); +require('codemirror/addon/mode/simple'); +require('codemirror/addon/dialog/dialog.css'); +require('codemirror/addon/dialog/dialog'); +require('codemirror/addon/scroll/annotatescrollbar'); +require('codemirror/addon/scroll/scrollpastend'); +require('codemirror/addon/scroll/simplescrollbars.css'); +require('codemirror/addon/scroll/simplescrollbars'); +require('codemirror/addon/search/jump-to-line'); +require('codemirror/addon/search/match-highlighter'); +require('codemirror/addon/search/matchesonscrollbar.css'); +require('codemirror/addon/search/matchesonscrollbar'); +require('codemirror/addon/search/search'); +require('codemirror/addon/search/searchcursor'); + +require('codemirror/mode/brainfuck/brainfuck'); +require('codemirror/mode/clike/clike'); +require('codemirror/mode/css/css'); +require('codemirror/mode/dart/dart'); +require('codemirror/mode/diff/diff'); +require('codemirror/mode/dockerfile/dockerfile'); +require('codemirror/mode/erlang/erlang'); +require('codemirror/mode/gfm/gfm'); +require('codemirror/mode/go/go'); +require('codemirror/mode/handlebars/handlebars'); +require('codemirror/mode/htmlembedded/htmlembedded'); +require('codemirror/mode/htmlmixed/htmlmixed'); +require('codemirror/mode/http/http'); +require('codemirror/mode/javascript/javascript'); +require('codemirror/mode/jsx/jsx'); +require('codemirror/mode/julia/julia'); +require('codemirror/mode/lua/lua'); +require('codemirror/mode/markdown/markdown'); +require('codemirror/mode/nginx/nginx'); +require('codemirror/mode/perl/perl'); +require('codemirror/mode/php/php'); +require('codemirror/mode/properties/properties'); +require('codemirror/mode/protobuf/protobuf'); +require('codemirror/mode/pug/pug'); +require('codemirror/mode/python/python'); +require('codemirror/mode/rpm/rpm'); +require('codemirror/mode/ruby/ruby'); +require('codemirror/mode/rust/rust'); +require('codemirror/mode/sass/sass'); +require('codemirror/mode/shell/shell'); +require('codemirror/mode/smarty/smarty'); +require('codemirror/mode/sql/sql'); +require('codemirror/mode/swift/swift'); +require('codemirror/mode/toml/toml'); +require('codemirror/mode/twig/twig'); +require('codemirror/mode/vue/vue'); +require('codemirror/mode/xml/xml'); +require('codemirror/mode/yaml/yaml'); + +const EditorContainer = styled.div` + min-height: 16rem; + height: calc(100vh - 20rem); + ${tw`relative`}; + + > div { + ${tw`rounded h-full`}; + } + + .CodeMirror { + font-size: 12px; + line-height: 1.375rem; + } + + .CodeMirror-linenumber { + padding: 1px 12px 0 12px !important; + } + + .CodeMirror-foldmarker { + color: #CBCCC6; + text-shadow: none; + margin-left: 0.25rem; + margin-right: 0.25rem; + } +`; + +export interface Props { + style?: React.CSSProperties; + initialContent?: string; + mode: string; + filename?: string; + onModeChanged: (mode: string) => void; + fetchContent: (callback: () => Promise) => void; + onContentSaved: () => void; +} + +const findModeByFilename = (filename: string) => { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + + if (info.file && info.file.test(filename)) { + return info; + } + } + + const dot = filename.lastIndexOf('.'); + const ext = dot > -1 && filename.substring(dot + 1, filename.length); + + if (ext) { + for (let i = 0; i < modes.length; i++) { + const info = modes[i]; + if (info.ext) { + for (let j = 0; j < info.ext.length; j++) { + if (info.ext[j] === ext) { + return info; + } + } + } + } + } + + return undefined; +}; + +export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { + const [ editor, setEditor ] = useState(); + + const ref = useCallback((node) => { + if (!node) return; + + const e = CodeMirror.fromTextArea(node, { + mode: 'text/plain', + theme: 'ayu-mirage', + indentUnit: 4, + smartIndent: true, + tabSize: 4, + indentWithTabs: false, + lineWrapping: true, + lineNumbers: true, + foldGutter: true, + fixedGutter: true, + scrollbarStyle: 'overlay', + coverGutterNextToScrollbar: false, + readOnly: false, + showCursorWhenSelecting: false, + autofocus: false, + spellcheck: true, + autocorrect: false, + autocapitalize: false, + lint: false, + // This property is actually used, the d.ts file for CodeMirror is incorrect. + // @ts-ignore + autoCloseBrackets: true, + matchBrackets: true, + gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + }); + + setEditor(e); + }, []); + + useEffect(() => { + if (filename === undefined) { + return; + } + + onModeChanged(findModeByFilename(filename)?.mime || 'text/plain'); + }, [ filename ]); + + useEffect(() => { + editor && editor.setOption('mode', mode); + }, [ editor, mode ]); + + useEffect(() => { + editor && editor.setValue(initialContent || ''); + }, [ editor, initialContent ]); + + useEffect(() => { + if (!editor) { + fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); + return; + } + + editor.addKeyMap({ + 'Ctrl-S': () => onContentSaved(), + 'Cmd-S': () => onContentSaved(), + }); + + fetchContent(() => Promise.resolve(editor.getValue())); + }, [ editor, fetchContent, onContentSaved ]); + + return ( + + -
-
- - -

The version of this package, or the version of the files contained within the package.

-
-
- - -

The option that this pack is associated with. Only servers that are assigned this option will be able to access this pack.

-
-
-
-
-
-
-
-

Pack Configuration

-
-
-
-
- - -
-

Check this box if user should be able to select this pack to install on their servers.

-
-
-
- - -
-

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.

-
-
-
- - -
-

Check this box if servers assigned this pack should not be able to switch to a different pack.

-
-
-
- - -

This package file must be a .tar.gz archive of pack files to be decompressed into the server folder.

-

If your file is larger than 50MB 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.

-
-

This node is currently configured with the following limits:
upload_max_filesize={{ ini_get('upload_max_filesize') }}
post_max_size={{ ini_get('post_max_size') }}

If your file is larger than either of those values this request will fail.

-
-
-
- -
-
-
- -@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/packs/view.blade.php b/resources/views/admin/packs/view.blade.php deleted file mode 100644 index 63382a21c..000000000 --- a/resources/views/admin/packs/view.blade.php +++ /dev/null @@ -1,154 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - Packs → View → {{ $pack->name }} -@endsection - -@section('content-header') -

{{ $pack->name }}{{ str_limit($pack->description, 60) }}

- -@endsection - -@section('content') -
-
-
-
-
-

Pack Details

-
-
-
- - -

A short but descriptive name of what this pack is. For example, Counter Strike: Source if it is a Counter Strike package.

-
-
- - -
-
- - -

The version of this package, or the version of the files contained within the package.

-
-
- - -

If you would like to modify the stored pack you will need to upload a new archive.tar.gz to the location defined above.

-
-
-
-
-
-
-
-

Pack Configuration

-
-
-
- - -

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 cannot be changed if servers are attached to this pack.

-
-
-
- selectable ?: 'checked' }}/> - -
-

Check this box if user should be able to select this pack to install on their servers.

-
-
-
- visible ?: 'checked' }}/> - -
-

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.

-
-
-
- locked ?: 'checked' }}/> - -
-

Check this box if servers assigned this pack should not be able to switch to a different pack.

-
-
- -
-
-
-
-
-
-
-
-

Servers Using This Pack

-
-
- - - - - - - - @foreach($pack->servers as $server) - - - - - - - @endforeach -
IDServer NameNodeOwner
{{ $server->uuidShort }}{{ $server->name }}{{ $server->node->name }}{{ $server->user->email }}
-
-
-
-
-
-
-
- {!! csrf_field() !!} - -
-
- {!! csrf_field() !!} - -
-
-
-@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index ce8226319..f76ab1c33 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -26,7 +26,7 @@
- +
diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 026be8be3..47f6d06ba 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -244,20 +244,13 @@

Select the Egg that will define how this server should operate.

- -
- - -

Select a data pack to be automatically installed on this server when first created.

-
-
-

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.

+

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.

@@ -334,7 +327,7 @@ // END Persist 'Service Variables' - {!! Theme::js('js/admin/new-server.js') !!} + {!! Theme::js('js/admin/new-server.js?v=20200913') !!}