diff --git a/README.md b/README.md index 3fab319b84..a88f180365 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) - -#### [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. - -#### [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). +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) + +| 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. | + +## 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 5f2a50e62e..f458b51dc1 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 @@ public function __construct( 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 @@ public function handle() $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 b02ef66a46..4002797332 100644 --- a/app/Contracts/Repository/AllocationRepositoryInterface.php +++ b/app/Contracts/Repository/AllocationRepositoryInterface.php @@ -6,14 +6,6 @@ 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 @@ public function getUniqueAllocationIpsForNode(int $node): Collection; */ 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 f1ab6e8040..0000000000 --- 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 5e216fed4f..0000000000 --- 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 f1608936c0..0000000000 --- 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 62b11136d8..0000000000 --- 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 8a6a82c200..0000000000 --- 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 5e285a8c6d..5a7bc1e307 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 @@ public function __construct(NodeRepository $repository, Factory $view) */ 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 ba9e2e9474..2121985c94 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeViewController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeViewController.php @@ -5,6 +5,7 @@ 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 @@ public function allocations(Request $request, Node $node) 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 98191222eb..0000000000 --- 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 31bee2cdca..a0b73f5524 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 @@ public function __construct( */ 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 f914013bb8..94df5dc21c 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -240,7 +240,7 @@ public function toggleInstall(Server $server) } /** - * 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 18e7e3b436..2afbe1407f 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -5,6 +5,7 @@ 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 @@ public function __construct( */ 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 @@ public function update(UserFormRequest $request, User $user) */ 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 98ec305746..62ab4ea453 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -5,6 +5,7 @@ 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 @@ public function __construct( */ 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 e6b4be6a2f..7198611ba3 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 @@ public function __construct( */ 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 @@ public function index(GetNodesRequest $request): array * 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 @@ public function store(StoreNodeRequest $request): JsonResponse * 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 @@ public function update(UpdateNodeRequest $request): array * 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 69f2706ac9..126c919216 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -5,6 +5,7 @@ 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 @@ public function __construct( */ 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 522cef8f46..c8b309d854 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -5,6 +5,7 @@ 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 @@ public function __construct( */ 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 @@ public function index(GetUsersRequest $request): array * 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 @@ public function store(StoreUserRequest $request): JsonResponse * 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 fa4818fc98..0000000000 --- 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 fc13c8f29d..b62d6e5c11 100644 --- a/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php +++ b/app/Http/Controllers/Api/Remote/SftpAuthenticationController.php @@ -14,6 +14,7 @@ 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 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse '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 @@ public function __invoke(SftpAuthenticationFormRequest $request): JsonResponse $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 8c10b22d26..505f1a3054 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\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 @@ public function handle(Request $request, Closure $next) } } - 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 68b3a8d36f..0000000000 --- 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 96e0eebe4d..6f930615c6 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -62,15 +62,6 @@ public function withValidator($validator) ], 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 32f68bd232..15780d695d 100644 --- a/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php +++ b/app/Http/Requests/Api/Application/Servers/StoreServerRequest.php @@ -36,7 +36,6 @@ public function rules(): array '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 @@ public function validated() '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 532ae1c35a..fc367cdaf4 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerStartupRequest.php @@ -31,7 +31,6 @@ public function rules(): array '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 @@ public function validated() 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 25dc4f1e1a..008b444361 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 59517621a2..072a74f9bb 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 fa5bb6a919..0000000000 --- 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 d76fed4945..016702141f 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 2c53c8536d..143fe95a8c 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -35,7 +35,6 @@ * @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 @@ public function variables() 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 ac0b5da9a4..023243f93b 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 0000000000..a897dd6dd5 --- /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 e8a260be80..2f2f6d26ff 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -5,7 +5,6 @@ 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 @@ * @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 @@ public function getConfiguration() 'bind_port' => $this->daemonSFTP, ], ], + 'allowed_mounts' => $this->mounts->pluck('source')->toArray(), 'remote' => route('index'), ]; } @@ -214,11 +202,19 @@ public function getJsonConfiguration(bool $pretty = false) */ 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 3846d74eb4..0000000000 --- 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 a7eb2709bb..f870866e2d 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 91ba9621a3..f6de3516c6 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 @@ * @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 @@ * @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 @@ * @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 @@ public function allocations() 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 @@ public function location() 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 @@ public function backups() /** * 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 d75bbe9abf..ab85b85dd7 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -99,14 +99,4 @@ public function permissions() { 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 d1ab0eb866..0000000000 --- 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 @@ public function servers() 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 ef8e6cb0a9..8a0434f524 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -6,7 +6,6 @@ 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\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\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 @@ public function register() // 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 @@ public function register() $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 8bb11a5528..0000000000 --- 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 cc7efb23bf..15a5db81bb 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 @@ public function model() 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 @@ public function getUniqueAllocationIpsForNode(int $node): Collection */ 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 @@ public function getAssignedAllocationIds(int $server): array * @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 @@ public function getDiscardableDedicatedAllocations(array $nodes = []): array */ 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 @@ public function getRandomAllocation(array $nodes, array $ports, bool $dedicated } if (! empty($whereIn)) { - $query->orWhereIn('port', $whereIn); + $inner->orWhereIn('port', $whereIn); } }); } @@ -128,12 +101,12 @@ public function getRandomAllocation(array $nodes, array $ports, bool $dedicated $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 2f11ff962a..0000000000 --- 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 34b5247af8..bd8133022d 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -289,12 +289,7 @@ public function updateOrCreate(array $where, array $fields, bool $validate = tru */ 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 @@ public function all(): Collection */ 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 47d5e321a4..6d14d5aa63 100644 --- a/app/Repositories/Eloquent/LocationRepository.php +++ b/app/Repositories/Eloquent/LocationRepository.php @@ -4,15 +4,12 @@ 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 c75ba2aa6c..286e07913d 100644 --- a/app/Repositories/Eloquent/MountRepository.php +++ b/app/Repositories/Eloquent/MountRepository.php @@ -5,14 +5,11 @@ 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 9c0fcf73c5..96b3df36fc 100644 --- a/app/Repositories/Eloquent/NestRepository.php +++ b/app/Repositories/Eloquent/NestRepository.php @@ -26,7 +26,7 @@ public function model() } /** - * 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 @@ public function model() */ 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 @@ public function getWithEggs(int $id = null) } /** - * Return a nest or all nests and the count of eggs, packs, and servers for that nest. + * Return a nest or all nests and the count of eggs and servers for that nest. * * @param int|null $id * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection @@ -59,7 +59,7 @@ public function getWithEggs(int $id = null) */ 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 2385c91096..b7463001eb 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -5,14 +5,11 @@ 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 @@ public function getUsageStatsRaw(Node $node): array })->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 bdd5eca6a8..0000000000 --- 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 f749d0d189..5e884a6614 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -5,7 +5,6 @@ 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 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInterface { - use Searchable; - /** * Return the model backing this repository. * @@ -25,19 +22,6 @@ public function model() 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 @@ public function loadEggRelations(Server $server, bool $refresh = false): Server */ 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 @@ public function getDataForRebuild(int $server = null, int $node = null): Collect */ 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 @@ public function getPrimaryAllocation(Server $server, bool $refresh = false): Ser */ 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 @@ public function loadDatabaseRelations(Server $server, bool $refresh = false): Se /** * 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 @@ public function getDaemonServiceData(Server $server, bool $refresh = false): arr $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 @@ public function getServersForPowerAction(array $servers = [], array $nodes = [], 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 b6468b5bb4..72a88efb01 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 @@ public function model() { 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 e1421b52c9..0000000000 --- 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 @@ public function css(string $resource): string */ 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 1403bfdca6..0000000000 --- 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 5179ea0dd8..0000000000 --- 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 cd9be47b68..0000000000 --- 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 52c32818cc..0000000000 --- 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 dbefd89a18..0000000000 --- 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 b6768fdb1c..86ff5bb691 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -159,7 +159,7 @@ private function processAllocations(Server $server, array &$data) // 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 fea2eaac03..f17405fad2 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 @@ public function handle(Server $server, bool $legacy = false): array * * @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 @@ protected function returnCurrentFormat(Server $server) '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 @@ protected function returnCurrentFormat(Server $server) ], '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 @@ protected function returnLegacyFormat(Server $server) ], '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 6d70d23e4e..589a790e99 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -246,7 +246,6 @@ private function createModel(array $data): Server '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 405fee47b4..7bec8fac66 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -140,7 +140,6 @@ private function updateAdministrativeSettings(array $data, Server &$server) '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 bc4d2486e1..6726e931fe 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -30,7 +30,10 @@ public function writeToEnvironment(array $values = []) $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 d54e77d209..7d24cc9748 100644 --- a/app/Transformers/Api/Application/LocationTransformer.php +++ b/app/Transformers/Api/Application/LocationTransformer.php @@ -25,7 +25,7 @@ public function getResourceName(): string } /** - * 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 e77bdd459d..0000000000 --- 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 d9df4263a2..e2c32eb766 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 @@ public function transform(Server $server): array '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 @@ public function includeUser(Server $server) 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 @@ public function includeVariables(Server $server) } /** - * 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 @@ public function includeLocation(Server $server) } /** - * 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 292cd6fcf3..0000000000 --- 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 acaba0c43e..84fcaf2d49 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 @@ public function transform(array $item) '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 63b0b9cb61..32fca75818 100644 --- a/config/database.php +++ b/config/database.php @@ -96,6 +96,8 @@ '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 @@ ], '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 671a64fd3a..aba0d729a1 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -10,7 +10,7 @@ | 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 @@ '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 @@ '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 @@ */ '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 @@ '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 6d6208f4f0..0525c30beb 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -32,7 +32,6 @@ 'io' => 500, 'cpu' => 0, 'oom_disabled' => 0, - 'pack_id' => null, 'installed' => 1, 'database_limit' => null, 'allocation_limit' => null, @@ -132,18 +131,6 @@ 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\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 ead68105c7..68eeee2ce9 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 689da89b68..92121b7df3 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 0000000000..b1d2c1bf18 --- /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 0000000000..879a64bdf3 --- /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 0000000000..2ab85a5de4 --- /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 0000000000..e418a85e95 --- /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 e939af8135..15e96c4bd4 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 3e0718a464..7416451daa 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 e3a175f62c..0000000000 --- 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 f1d58c6c68..4d44c4ff94 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -4,7 +4,7 @@ '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 @@ '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 77e44bce83..1caf6bf9d6 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 595f2b9c82..f17787e030 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 4f122a82dd..384a32edcf 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 304fe5630d..33f0eb5617 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 d495400b43..98e7a8d532 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 1e1e702ca8..f8b13eda29 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 a504f1951b..5599327143 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 47fba4edbf..0000000000 --- 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 0000000000..50b5449c51 --- /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 63382a21c3..0000000000 --- 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 ce82263190..f76ab1c33f 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 026be8be31..47f6d06ba5 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 @@ function serviceVariablesUpdated(eggId, ids) { // END Persist 'Service Variables' - {!! Theme::js('js/admin/new-server.js') !!} + {!! Theme::js('js/admin/new-server.js?v=20200913') !!}