From 06fe47fc3cec61880979978ff29b7242213e453f Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 25 Mar 2021 00:03:39 +0200 Subject: [PATCH 01/51] increase short limit --- database/Seeders/eggs/rust/egg-rust.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index 00523ac17..6d885b90f 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -31,7 +31,7 @@ "default_value": "A Rust Server", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:40" + "rules": "required|string|max:60" }, { "name": "OxideMod", From 48ad8f538e50548c78f092085affd56d375af69d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 26 Mar 2021 09:03:51 -0700 Subject: [PATCH 02/51] Always allow specifying a page size with the API; closes #3218 --- .../Api/Application/Locations/LocationController.php | 2 +- app/Http/Controllers/Api/Application/Nests/NestController.php | 2 +- .../Api/Application/Nodes/AllocationController.php | 3 ++- app/Http/Controllers/Api/Application/Nodes/NodeController.php | 2 +- .../Api/Application/Nodes/NodeDeploymentController.php | 2 +- .../Controllers/Api/Application/Servers/ServerController.php | 2 +- app/Http/Controllers/Api/Application/Users/UserController.php | 2 +- app/Services/Deployment/FindViableNodesService.php | 4 ++-- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index cdcf013b3..b7e82396a 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -65,7 +65,7 @@ class LocationController extends ApplicationApiController $locations = QueryBuilder::for(Location::query()) ->allowedFilters(['short', 'long']) ->allowedSorts(['id']) - ->paginate(100); + ->paginate($request->query('per_page') ?? 50); return $this->fractal->collection($locations) ->transformWith($this->getTransformer(LocationTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index 92de473e8..b66872d23 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -30,7 +30,7 @@ class NestController extends ApplicationApiController */ public function index(GetNestsRequest $request): array { - $nests = $this->repository->paginated(50); + $nests = $this->repository->paginated($request->query('per_page') ?? 50); return $this->fractal->collection($nests) ->transformWith($this->getTransformer(NestTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index c66701494..5a66e8175 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -43,7 +43,7 @@ class AllocationController extends ApplicationApiController */ public function index(GetAllocationsRequest $request, Node $node): array { - $allocations = $node->allocations()->paginate(50); + $allocations = $node->allocations()->paginate($request->query('per_page') ?? 50); return $this->fractal->collection($allocations) ->transformWith($this->getTransformer(AllocationTransformer::class)) @@ -53,6 +53,7 @@ class AllocationController extends ApplicationApiController /** * Store new allocations for a given node. * + * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index 61f27b822..2daf187c2 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -64,7 +64,7 @@ class NodeController extends ApplicationApiController $nodes = QueryBuilder::for(Node::query()) ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) ->allowedSorts(['id', 'uuid', 'memory', 'disk']) - ->paginate(100); + ->paginate($request->query('per_page') ?? 50); return $this->fractal->collection($nodes) ->transformWith($this->getTransformer(NodeTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index 1714bf7ca..9d5c75a05 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -37,7 +37,7 @@ class NodeDeploymentController extends ApplicationApiController $nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? []) ->setMemory($data['memory']) ->setDisk($data['disk']) - ->handle($request->input('page') ?? 0); + ->handle($request->query('per_page'), $request->query('page')); return $this->fractal->collection($nodes) ->transformWith($this->getTransformer(NodeTransformer::class)) diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 795999721..e0e679930 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -56,7 +56,7 @@ class ServerController extends ApplicationApiController $servers = QueryBuilder::for(Server::query()) ->allowedFilters(['uuid', 'name', 'image', 'external_id']) ->allowedSorts(['id', 'uuid']) - ->paginate(100); + ->paginate($request->query('per_page') ?? 50); 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 41a13f66c..0a5675e2d 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -66,7 +66,7 @@ class UserController extends ApplicationApiController $users = QueryBuilder::for(User::query()) ->allowedFilters(['email', 'uuid', 'username', 'external_id']) ->allowedSorts(['id', 'uuid']) - ->paginate(100); + ->paginate($request->query('per_page') ?? 50); return $this->fractal->collection($users) ->transformWith($this->getTransformer(UserTransformer::class)) diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index 76cb89abf..5c5a5138e 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -83,7 +83,7 @@ class FindViableNodesService * * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ - public function handle(int $page = null) + public function handle(int $perPage = null, int $page = null) { Assert::integer($this->disk, 'Disk space must be an int, got %s'); Assert::integer($this->memory, 'Memory usage must be an int, got %s'); @@ -103,7 +103,7 @@ class FindViableNodesService ->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [$this->disk]); if (!is_null($page)) { - $results = $results->paginate(50, ['*'], 'page', $page); + $results = $results->paginate($perPage ?? 50, ['*'], 'page', $page); } else { $results = $results->get()->toBase(); } From b5f5185a9b8ab40ca6387c5b29e0e83d97501cb1 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 26 Mar 2021 09:18:54 -0700 Subject: [PATCH 03/51] Update CHANGELOG.md --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eb2570eb..f070441be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.3.2 +### Fixed +* Fixes self-upgrade incorrectly executing the command to un-tar downloaded archives. +* Fixes the checkbox to delete all files when restoring a backup not actually passing that along in the API call. Files will now properly be deleted when restoring if selected. +* Fixes some keybindings not working correctly in the server console on Windows machines. +* Fixes mobile UI incorrectly squishing the Docker image selector on the server settings page. +* Fixes recovery tokens not having a `created_at` value set on them properly when they are created. +* Fixes flawed migration that would not correctly set the month value into schedule crons. +* Fixes incorrect mounting for Docker compose file that would cause error logs to be missing. + +### Changed +* Server resource lookups are now cached on the Panel for 20 seconds at a time to reduce the load from multiple clients requesting the same server's stats. +* Bungeecord egg no longer force-enables the query listener. +* Adds page to the dashboard URL to allow easy loading of a specific pagination page rather than resetting back to the first page when refreshing. +* All application API endpoints now correctly support the `?per_page=N` query parameter to specify how many resources to return at once. + ## v1.3.1 ### Fixed * Fixes the Rust egg not properly seeding during the upgrade & installation process. From a072b9c5be9c1103d7fa6a2910805f1808fd9a75 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 26 Mar 2021 09:30:13 -0700 Subject: [PATCH 04/51] =?UTF-8?q?Update=20license=20year,=20right=20on=20t?= =?UTF-8?q?ime=20=F0=9F=91=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE.md | 2 +- README.md | 2 +- resources/scripts/components/auth/LoginFormContainer.tsx | 2 +- resources/scripts/components/elements/PageContentBlock.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 11eabac9f..1fb886e97 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ # The MIT License (MIT) ``` -Copyright (c) 2015 - 2020 Dane Everitt +Copyright (c) 2015 - 2021 Dane Everitt and 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 diff --git a/README.md b/README.md index d98247f9b..83c0d390d 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ and there are plenty more games available provided by the community. Some of the ## License ``` -Copyright (c) 2015 - 2020 Dane Everitt & Contributors +Copyright (c) 2015 - 2021 Dane Everitt and 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 diff --git a/resources/scripts/components/auth/LoginFormContainer.tsx b/resources/scripts/components/auth/LoginFormContainer.tsx index 2423e0251..e54a50b75 100644 --- a/resources/scripts/components/auth/LoginFormContainer.tsx +++ b/resources/scripts/components/auth/LoginFormContainer.tsx @@ -47,7 +47,7 @@ export default forwardRef(({ title, ...props }, ref) =>

- © 2015 - 2020  + © 2015 - {(new Date()).getFullYear()}  = ({ title, showFlashKey

- © 2015 - 2020  + © 2015 - {(new Date()).getFullYear()}  Date: Fri, 26 Mar 2021 09:50:46 -0700 Subject: [PATCH 05/51] Cleanup and improve build docs --- .sami.php | 16 ------------ .styleci.yml | 7 ------ .travis.yml | 36 --------------------------- BUILDING.md | 46 +++++++++++++++++++--------------- CONTRIBUTING.md | 65 +++++++++++++++++++++++++++++-------------------- CONTRIBUTORS.md | 14 ----------- codecov.yml | 5 ---- 7 files changed, 64 insertions(+), 125 deletions(-) delete mode 100644 .sami.php delete mode 100644 .styleci.yml delete mode 100644 .travis.yml delete mode 100644 CONTRIBUTORS.md delete mode 100644 codecov.yml diff --git a/.sami.php b/.sami.php deleted file mode 100644 index da4c4658f..000000000 --- a/.sami.php +++ /dev/null @@ -1,16 +0,0 @@ -files() - ->name('*.php') - ->in($dir = __DIR__ . '/app'); - -return new Sami($iterator, array( - 'title' => 'Pterodactyl', - 'build_dir' => __DIR__ . '/.sami/build', - 'cache_dir' => __DIR__ . '/.sami/cache', - 'default_opened_level' => 2, -)); diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 87848d020..000000000 --- a/.styleci.yml +++ /dev/null @@ -1,7 +0,0 @@ -preset: laravel -risky: false -disabled: - - concat_without_spaces -enabled: - - concat_with_spaces - - no_unused_imports diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 06d9924cb..000000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: php -dist: trusty -git: - depth: 3 - quiet: true -matrix: - fast_finish: true - allow_failures: - - env: TEST_SUITE=Coverage -env: - matrix: - - TEST_SUITE=Unit - - TEST_SUITE=Coverage - - TEST_SUITE=Integration -php: - - 7.4 -sudo: false -cache: - directories: - - $HOME/.composer/cache -services: - - mysql -before_install: - - mysql -e 'CREATE DATABASE IF NOT EXISTS travis;' -before_script: - - echo 'opcache.enable_cli=1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - cp .env.travis .env - - travis_retry composer install --no-interaction --prefer-dist --no-suggest -script: - - if [ "$TEST_SUITE" = "Unit" ]; then vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit; fi; - - if [ "$TEST_SUITE" = "Coverage" ]; then vendor/bin/phpunit --bootstrap vendor/autoload.php --coverage-clover coverage.xml tests/Unit; fi; - - if [ "$TEST_SUITE" = "Integration" ]; then vendor/bin/phpunit tests/Integration; fi; -notifications: - email: false -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/BUILDING.md b/BUILDING.md index 286cc3c65..df60f70a8 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,10 +1,13 @@ # Local Development -Pterodactyl is now powered by Vuejs and Tailwindcss and uses webpack at its core to generate compiled assets. Release -versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go. +Pterodactyl is now powered by React, Typescript, and Tailwindcss using webpack at its core to generate compiled assets. +Release versions of Pterodactyl will include pre-compiled, minified, and hashed assets ready-to-go. -However, if you are interested in running custom themes or making modifications to the Vue files you'll need a build -system in place to generate these compiled assets. To get your environment setup, you'll first need to install at least Nodejs -`8`, and it is _highly_ recommended that you also install [Yarn](https://yarnpkg.com) to manage your `node_modules`. +However, if you are interested in running custom themes or making modifications to the React files you'll need a build +system in place to generate these compiled assets. To get your environment setup you'll need at minimum: + +* Node.js 12 +* [Yarn](https://classic.yarnpkg.com/lang/en/) v1 +* [Go](https://golang.org/) 1.15. ### Install Dependencies ```bash @@ -12,17 +15,19 @@ yarn install ``` The command above will download all of the dependencies necessary to get Pterodactyl assets building. After that, its as -simple as running the command below to generate assets while you're developing. +simple as running the command below to generate assets while you're developing. Until you've run this command at least +once you'll likely see a 500 error on your Panel about a missing `manifest.json` file. This is generated by the commands +below. ```bash -# build the compiled assets for development +# Build the compiled set of assets for development. yarn run build -# build the assets automatically when files are modified +# Build the assets automatically as they are changed. This allows you to refresh +# the page and see the changes immediately. yarn run watch ``` - ### Hot Module Reloading For more advanced users, we also support 'Hot Module Reloading', allowing you to quickly see changes you're making to the Vue template files without having to reload the page you're on. To Get started with this, you just need @@ -37,21 +42,22 @@ is the `--host` flag, which is required and should point to the machine where th The second is the `PUBLIC_PATH` environment variable which is the URL pointing to the HMR server and is appended to all of the asset URLs used in Pterodactyl. -#### Vagrant -If you want to use HMR with our Vagrant image, you can use `yarn run v:serve` as a shortcut for the correct parameters. -In order to have proper file change detection you can use the [`vagrant-notify-forwarder`](https://github.com/mhallin/vagrant-notify-forwarder) to notify file events from the host to the VM. -```sh -vagrant plugin install vagrant-notify-forwarder -vagrant reload -``` +#### Development Environment +If you're using the [`pterodactyl/development`](https://github.com/pterodactyl/development) environments, which are +highly recommended, you can just run `yarn run serve` to run the HMR server, no additional configuration is necessary. ### Building for Production -Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified, and -hashed assets to push live. To do so, run the command below: +Once you have your files squared away and ready for the live server, you'll be needing to generate compiled, minified, +and hashed assets to push live. To do so, run the command below: ```bash yarn run build:production ``` -This will generate a production ready `bundle.js` and `bundle.css` as well as a `manifest.json` and store them in -the `/public/assets` directory where they can then be access by clients, and read by the Panel. +This will generate a production JS bundle and associated assets, all located in `public/assets/` which will need to +be uploaded to your server or CDN for clients to use. + +### Running Wings +To run `wings` in development all you need to do is set up the configuration file as normal when adding a new node, and +then you can build and run a local version of Wings by executing `make debug` in the Wings code directory. This must +be run on a Linux VM of some sort, you cannot run this locally on macOS or Windows. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 443eceab4..775dcd5d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,44 +1,55 @@ # Contributing -We're glad you want to help us out and make this panel the best that it can be! We have a few simple things to follow when making changes to files and adding new features. +We're glad you want to help us out and make this panel the best that it can be! We have a few simple things to follow +when making changes to files and adding new features. + +### Development Environment +Please check the [`pterodactyl/development`](https://github.com/pterodactyl/development) repository for a Vagrant & +Docker setup that should run on most macOS and Linux distributions. In the event that your platform is not supported +you're welcome to open a PR, or just take a look at our setup scripts to see what you'll need to successfully develop +with Pterodactyl. + +#### Building Assets +Please see [`BUILDING.md`](https://github.com/pterodactyl/panel/blob/develop/BUILDING.md) for details on how to actually +build and run the development server. ### Project Branches This section mainly applies to those with read/write access to our repositories, but can be helpful for others. -The `develop` branch should always be in a runnable state, and not contain any major breaking features. For the most part, this means you will need to create `feature/` branches in order to add new functionality or change how things work. When making a feature branch, if it is referencing something in the issue tracker, please title the branch `feature/PTDL-###` where `###` is the issue number. +The `develop` branch should always be in a runnable state, and not contain any major breaking features. For the most +part, this means you will need to create `feature/` branches in order to add new functionality or change how things +work. When making a feature branch, if it is referencing something in the issue tracker, please title the branch +`feature/PTDL-###` where `###` is the issue number. -Moving forward all commits from contributors should be in the form of a PR, unless it is something we have previously discussed as being able to be pushed right into `develop`. +All new code should contain tests to ensure their functionality is not unintentionally changed down the road. This +is especially important for any API actions or authentication based controls. -All new code should contain unit tests at a minimum (where applicable). There is a lot of uncovered code currently, so as you are doing things please be looking for places that you can write tests. - -### Update the CHANGELOG -When adding something that is new, fixed, changed, or security-related for the next release you should be adding a note to the CHANGELOG. If something is changing within the same version (i.e. fixing a bug introduced but not released) it should _not_ go into the CHANGELOG. +### The CHANGELOG +You should not make any changes to the `CHANGELOG.md` file during your code updates. This is updated by the maintainers +at the time of deployment to include the relevant changes that were made. ### Code Guidelines -We are a `PSR-4` and `PSR-0` compliant project, so please follow those guidelines at a minimum. In addition, StyleCI runs on all of our code to ensure the formatting is standardized across everything. When a PR is made StyleCI will analyze your code and make a pull to that branch if necessary to fix any formatting issues. This project also ships with a PHP-CS configuration file and you are welcome to configure your local environment to make use of that. +We are a `PSR-4` and `PSR-0` compliant project, so please follow those guidelines at a minimum. In addition we run +`php-cs-fixer` on all PRs and releases to enforce a consistent code style. The following command executed on your machine +should show any areas where the code style does not line up correctly. -All class variable declarations should be in alphabetical order, and constructor arguments should be in alphabetical order based on the classname. See the example below for how this should look, or check out any of the `app/Service` files for examples. - -```php -class ProcessScheduleService -{ - protected $repository; - protected $runnerService; - - public function __construct(RunTaskService $runnerService, ScheduleRepositoryInterface $repository) - { - $this->repository = $repository; - $this->runnerService = $runnerService; - } +``` +vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format=udiff --config .php_cs.dist ``` ### Responsible Disclosure -This is a fairly in-depth project and makes use of a lot of parts. We strive to keep everything as secure as possible and welcome you to take a look at the code provided in this project yourself. We do ask that you be considerate of others who are using the software and not publicly disclose security issues without contacting us first by email. +This is a fairly in-depth project and makes use of a lot of parts. We strive to keep everything as secure as possible +and welcome you to take a look at the code provided in this project yourself. We do ask that you be considerate of +others who are using the software and not publicly disclose security issues without contacting us first by email. -We'll make a deal with you: if you contact us by email and we fail to respond to you within a week you are welcome to publicly disclose whatever issue you have found. We understand how frustrating it is when you find something big and no one will respond to you. This holds us to a standard of providing prompt attention to any issues that arise and keeping this community safe. +We'll make a deal with you: if you contact us by email and we fail to respond to you within a week you are welcome to +publicly disclose whatever issue you have found. We understand how frustrating it is when you find something big and +no one will respond to you. This holds us to a standard of providing prompt attention to any issues that arise and +keeping this community safe. -If you've found what you believe is a security issue please email us at `support@pterodactyl.io`. +If you've found what you believe is a security issue please email `dane åt pterodactyl døt io`. -### Where to find Us -You can find us in a couple places online. First and foremost, we're active right here on Github. If you encounter a bug or other problems, open an issue on here for us to take a look at it. We also accept feature requests here as well. +### Contact Us +You can find us in a couple places online. First and foremost, we're active right here on Github. If you encounter a +bug or other problems, open an issue on here for us to take a look at it. We also accept feature requests here as well. -You can also find us on [Discord](https://pterodactyl.io/discord). In the event that you need to get in contact with us privately feel free to contact us at `support@pterodactyl.io`. Try not to email us with requests for support regarding the panel, we'll probably just direct you to our Discord. +You can also find us on [Discord](https://discord.gg/pterodactyl). diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index 906d00d50..000000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,14 +0,0 @@ -# Pterodactyl Panel Contributors -This panel would not be possible without the support of our wonderful community of -developers who provide code enhancements, new features, and bug fixes to make this panel -the best that is can be. You can view a full listing of contributors [here](https://github.com/Pterodactyl/Panel/graphs/contributors). - -Dane Everitt [@DaneEveritt](https://github.com/Pterodactyl/Panel/commits?author=DaneEveritt) - -Dylan Seidt [@DDynamic](https://github.com/Pterodactyl/Panel/commits?author=DDynamic) - -[@nikkiii](https://github.com/Pterodactyl/Panel/commits?author=nikkiii) - -# Get Involved -See our `CONTRIBUTING.md` document for information on how to get started. Once you've submitted some code feel free to -modify this file and add your name to the list. Please follow the format above for your name and linking to your contributions. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index fdc1d707a..000000000 --- a/codecov.yml +++ /dev/null @@ -1,5 +0,0 @@ -coverage: - status: - project: off - patch: off -comment: false From fe8136de22297e3558dccd4800daa87dba30ceff Mon Sep 17 00:00:00 2001 From: MineDoubleSpace Date: Sat, 27 Mar 2021 16:41:14 +1300 Subject: [PATCH 06/51] add BisectHosting to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 83c0d390d..2c94174a2 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. | | [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | | [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! | +| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | ## Documentation * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) From 58d31fdd4ae086e3a20e5f9a0467262bf180b922 Mon Sep 17 00:00:00 2001 From: Kai Devrim Date: Fri, 26 Mar 2021 21:27:18 -0700 Subject: [PATCH 07/51] Change relative docker compose to absolute path docker-compose The old version was relative and led to a 404 because the compose file is in /panel. --- .github/docker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/docker/README.md b/.github/docker/README.md index c5c2991c7..434e509d6 100644 --- a/.github/docker/README.md +++ b/.github/docker/README.md @@ -2,7 +2,7 @@ This is a ready to use docker image for the panel. ## Requirements -This docker image requires some additional software to function. The software can either be provided in other containers (see the [docker-compose.yml](docker-compose.yml) as an example) or as existing instances. +This docker image requires some additional software to function. The software can either be provided in other containers (see the [docker-compose.yml](https://github.com/pterodactyl/panel/blob/develop/docker-compose.example.yml) as an example) or as existing instances. A mysql database is required. We recommend the stock [MariaDB Image](https://hub.docker.com/_/mariadb/) image if you prefer to run it in a docker container. As a non-containerized option we recommend mariadb. @@ -73,4 +73,4 @@ Every driver requires `MAIL_FROM` to be set. | mandrill | [Mandrill](http://www.mandrill.com/) | `MAIL_USERNAME` | | postmark | [Postmark](https://postmarkapp.com/) | `MAIL_USERNAME` | | mailgun | [Mailgun](https://www.mailgun.com/) | `MAIL_USERNAME`, `MAIL_HOST` | -| smtp | Any SMTP server can be configured | `MAIL_USERNAME`, `MAIL_HOST`, `MAIL_PASSWORD`, `MAIL_PORT` | \ No newline at end of file +| smtp | Any SMTP server can be configured | `MAIL_USERNAME`, `MAIL_HOST`, `MAIL_PASSWORD`, `MAIL_PORT` | From 9e4286a5480b78169110d681ce685112b4de6849 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Sun, 28 Mar 2021 12:38:25 +1100 Subject: [PATCH 08/51] docker: set errexit in the entrypoint script Currently container startup will ignore any errors, which will tend to leave things in a broken state if operations like migrations or certificate provisioning fail. Prefer to terminate the container rather than try to limp on. --- .github/docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh index 6486b2c12..e6e1b0966 100644 --- a/.github/docker/entrypoint.sh +++ b/.github/docker/entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/ash +#!/bin/ash -e cd /app mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \ From 95f26bd0c4e1cc3bd396466e5cb0681a9cb0ed1e Mon Sep 17 00:00:00 2001 From: wingdings255 <46852882+wingdings255@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:19:23 +0000 Subject: [PATCH 09/51] Update egg-ark--survival-evolved.json Updated the list of available ark maps to include the new CrystalIsles map --- .../Seeders/eggs/source-engine/egg-ark--survival-evolved.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json b/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json index 03305ffa6..3de0d690a 100644 --- a/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json +++ b/database/Seeders/eggs/source-engine/egg-ark--survival-evolved.json @@ -47,7 +47,7 @@ }, { "name": "Server Map", - "description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis", + "description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles", "env_variable": "SERVER_MAP", "default_value": "TheIsland", "user_viewable": true, From 6034440061e609101ea483709907aa3ab3e9bd3d Mon Sep 17 00:00:00 2001 From: harryfrommixmlhosting <78830913+harryfrommixmlhosting@users.noreply.github.com> Date: Sat, 3 Apr 2021 02:17:25 +0100 Subject: [PATCH 10/51] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2c94174a2..e5643f1e4 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | Company | About | | ------- | ----- | | [**WISP**](https://wisp.gg) | Extra features. | +| [**MixmlHosting**](https://mixmlhosting.com) | MixmlHosting provides high quality Virtual Private Servers along with game servers, all at a affordable price. | | [**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. | | [**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. | From 45680cab47bbc5ca88afa636a53dd1f56dbe8d19 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 3 Apr 2021 10:53:41 -0700 Subject: [PATCH 11/51] Don't use tagging, closes #3224 --- .../Api/Client/Servers/ResourceUtilizationController.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index 72901c8bd..5b80768b2 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -42,11 +42,10 @@ class ResourceUtilizationController extends ClientApiController */ public function __invoke(GetServerRequest $request, Server $server): array { - $stats = $this->cache - ->tags(['resources']) - ->remember($server->uuid, Carbon::now()->addSeconds(20), function () use ($server) { - return $this->repository->setServer($server)->getDetails(); - }); + $key = "resources:{$server->uuid}"; + $stats = $this->cache->remember($key, Carbon::now()->addSeconds(20), function () use ($server) { + return $this->repository->setServer($server)->getDetails(); + }); return $this->fractal->item($stats) ->transformWith($this->getTransformer(StatsTransformer::class)) From c71c1e57db5f08034666a387cebcddcf8237767f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 3 Apr 2021 11:18:16 -0700 Subject: [PATCH 12/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e5643f1e4..ace16213a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | ------- | ----- | | [**WISP**](https://wisp.gg) | Extra features. | | [**MixmlHosting**](https://mixmlhosting.com) | MixmlHosting provides high quality Virtual Private Servers along with game servers, all at a affordable price. | +| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | | [**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. | | [**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. | @@ -33,7 +34,6 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. | | [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | | [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! | -| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. | ## Documentation * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) From 52308af74d6b9855d08c08cd7cfcf5f5b8f3406d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 4 Apr 2021 09:45:20 -0700 Subject: [PATCH 13/51] Normalize this code. --- .../server/schedules/ScheduleEditContainer.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx index 8fcb91af0..df0af5b99 100644 --- a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx @@ -46,9 +46,9 @@ const ActivePill = ({ active }: { active: boolean }) => ( ); export default () => { - const params = useParams() as Params; const history = useHistory(); - const state: State = useLocation().state; + const { state } = useLocation(); + const { id: scheduleId } = useParams(); const id = ServerContext.useStoreState(state => state.server.data!.id); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); @@ -61,20 +61,20 @@ export default () => { const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); useEffect(() => { - if (schedule?.id === Number(params.id)) { + if (schedule?.id === Number(scheduleId)) { setIsLoading(false); return; } clearFlashes('schedules'); - getServerSchedule(uuid, Number(params.id)) + getServerSchedule(uuid, Number(scheduleId)) .then(schedule => appendSchedule(schedule)) .catch(error => { console.error(error); clearAndAddHttpError({ error, key: 'schedules' }); }) .then(() => setIsLoading(false)); - }, [ params ]); + }, [ scheduleId ]); const toggleEditModal = useCallback(() => { setShowEditModal(s => !s); From 18e5ce310a3e439da19fb8e6084f977bafa14ea6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 4 Apr 2021 10:25:54 -0700 Subject: [PATCH 14/51] Use updated response from wings --- app/Transformers/Api/Client/StatsTransformer.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Transformers/Api/Client/StatsTransformer.php b/app/Transformers/Api/Client/StatsTransformer.php index 8ca7bea07..bbe5afe86 100644 --- a/app/Transformers/Api/Client/StatsTransformer.php +++ b/app/Transformers/Api/Client/StatsTransformer.php @@ -21,13 +21,13 @@ class StatsTransformer extends BaseClientTransformer { return [ 'current_state' => Arr::get($data, 'state', 'stopped'), - 'is_suspended' => Arr::get($data, 'suspended', false), + 'is_suspended' => Arr::get($data, 'is_suspended', false), 'resources' => [ - 'memory_bytes' => Arr::get($data, 'memory_bytes', 0), - 'cpu_absolute' => Arr::get($data, 'cpu_absolute', 0), - 'disk_bytes' => Arr::get($data, 'disk_bytes', 0), - 'network_rx_bytes' => Arr::get($data, 'network.rx_bytes', 0), - 'network_tx_bytes' => Arr::get($data, 'network.tx_bytes', 0), + 'memory_bytes' => Arr::get($data, 'utilization.memory_bytes', 0), + 'cpu_absolute' => Arr::get($data, 'utilization.cpu_absolute', 0), + 'disk_bytes' => Arr::get($data, 'utilization.disk_bytes', 0), + 'network_rx_bytes' => Arr::get($data, 'utilization.network.rx_bytes', 0), + 'network_tx_bytes' => Arr::get($data, 'utilization.network.tx_bytes', 0), ], ]; } From f973285e04556e08607ba5cd56bb751c29082cf9 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 4 Apr 2021 10:45:33 -0700 Subject: [PATCH 15/51] Guard against unexpected panic conditions from wings --- .../Http/Connection/DaemonConnectionException.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/Exceptions/Http/Connection/DaemonConnectionException.php b/app/Exceptions/Http/Connection/DaemonConnectionException.php index eba5f8a7f..c308b9b72 100644 --- a/app/Exceptions/Http/Connection/DaemonConnectionException.php +++ b/app/Exceptions/Http/Connection/DaemonConnectionException.php @@ -38,6 +38,15 @@ class DaemonConnectionException extends DisplayException if ($useStatusCode) { $this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode(); + // There are rare conditions where wings encounters a panic condition and crashes the + // request being made after content has already been sent over the wire. In these cases + // you can end up with a "successful" response code that is actual an error. + // + // Handle those better here since we shouldn't ever end up in this exception state and + // be returning a 2XX level response. + if ($this->statusCode < 400) { + $this->statusCode = Response::HTTP_BAD_GATEWAY; + } } if (is_null($response)) { From 77a3ca682f1ecd9e52655b1dfe12f0a7996adb27 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Thu, 8 Apr 2021 17:34:25 -0400 Subject: [PATCH 16/51] Change to actual function names to support MariaDB --- .../Controllers/Api/Remote/Servers/ServerDetailsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index 3bf36a5ae..7e3790e78 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -111,7 +111,7 @@ class ServerDetailsController extends Controller /** @var \Pterodactyl\Models\Server[] $servers */ $servers = Server::query() ->select('servers.*') - ->selectRaw('started.metadata->>"$.backup_uuid" as backup_uuid') + ->selectRaw('JSON_UNQUOTE(JSON_EXTRACT(started.metadata, "$.backup_uuid")) as backup_uuid') ->leftJoinSub(function (Builder $builder) { $builder->select('*')->from('audit_logs') ->where('action', AuditLog::SERVER__BACKUP_RESTORE_STARTED) From f5ca3914004630f28979b2bf6d06fca21f9fa6c8 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 17 Apr 2021 12:02:08 -0700 Subject: [PATCH 17/51] Get tests back in working order --- .../Api/Application/Location/LocationControllerTest.php | 4 ++-- .../Integration/Api/Application/Users/UserControllerTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Integration/Api/Application/Location/LocationControllerTest.php b/tests/Integration/Api/Application/Location/LocationControllerTest.php index 150e01bea..c871e4073 100644 --- a/tests/Integration/Api/Application/Location/LocationControllerTest.php +++ b/tests/Integration/Api/Application/Location/LocationControllerTest.php @@ -18,7 +18,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase { $locations = Location::factory()->times(2)->create(); - $response = $this->getJson('/api/application/locations'); + $response = $this->getJson('/api/application/locations?per_page=60'); $response->assertStatus(Response::HTTP_OK); $response->assertJsonCount(2, 'data'); $response->assertJsonStructure([ @@ -38,7 +38,7 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase 'pagination' => [ 'total' => 2, 'count' => 2, - 'per_page' => 100, + 'per_page' => 60, 'current_page' => 1, 'total_pages' => 1, ], diff --git a/tests/Integration/Api/Application/Users/UserControllerTest.php b/tests/Integration/Api/Application/Users/UserControllerTest.php index 00b091589..f084d2ed2 100644 --- a/tests/Integration/Api/Application/Users/UserControllerTest.php +++ b/tests/Integration/Api/Application/Users/UserControllerTest.php @@ -18,7 +18,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase { $user = User::factory()->create(); - $response = $this->getJson('/api/application/users'); + $response = $this->getJson('/api/application/users?per_page=60'); $response->assertStatus(Response::HTTP_OK); $response->assertJsonCount(2, 'data'); $response->assertJsonStructure([ @@ -38,7 +38,7 @@ class UserControllerTest extends ApplicationApiIntegrationTestCase 'pagination' => [ 'total' => 2, 'count' => 2, - 'per_page' => 100, + 'per_page' => 60, 'current_page' => 1, 'total_pages' => 1, ], From 1ebcb2b7fb50459e2165046b653759e95cfb639d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 17 Apr 2021 12:10:40 -0700 Subject: [PATCH 18/51] Run tests on both MariaDB & MySQL --- .github/workflows/tests.yml | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d47663334..8c1738075 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,17 +1,25 @@ -name: Run Test Suite +name: run tests on: push: - branch-ignore: - - 'master' - - 'release/**' + branches-ignore: + - master + - "release/**" pull_request: jobs: - integration_tests: + tests: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" services: + mariadb: + image: mariadb:10.2 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: panel_test + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 mysql: - image: mysql:5.7 + image: mysql:8 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: panel_test @@ -21,7 +29,8 @@ jobs: strategy: fail-fast: false matrix: - php: [7.4, 8.0] + php: [ 7.4, 8.0 ] + database: [ mysql, mariadb ] name: PHP ${{ matrix.php }} steps: - name: checkout @@ -59,9 +68,15 @@ jobs: env: DB_CONNECTION: testing TESTING_DB_HOST: UNIT_NO_DB - - name: execute integration tests + - name: execute integration tests (mysql) run: vendor/bin/phpunit tests/Integration - if: ${{ always() }} + if: "${{ matrix.database }} == 'mysql'" env: TESTING_DB_PORT: ${{ job.services.mysql.ports[3306] }} TESTING_DB_USERNAME: root + - name: execute integration tests (mariadb) + run: vendor/bin/phpunit tests/Integration + if: "${{ matrix.database }} == 'mariadb'" + env: + TESTING_DB_PORT: ${{ job.services.mariadb.ports[3306] }} + TESTING_DB_USERNAME: root From e2e62b2dae8256ae45301c3588d7c9446b1d7bf5 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 17 Apr 2021 12:12:04 -0700 Subject: [PATCH 19/51] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c1738075..d07844386 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: matrix: php: [ 7.4, 8.0 ] database: [ mysql, mariadb ] - name: PHP ${{ matrix.php }} + name: "php-${{ matrix.php }} (engine: ${{ matrix.database }})" steps: - name: checkout uses: actions/checkout@v2 From eb582f51f182af2dde072560b3ec638781c0e3a8 Mon Sep 17 00:00:00 2001 From: aussieserverhosts <65438932+aussieserverhosts@users.noreply.github.com> Date: Sun, 18 Apr 2021 05:18:13 +1000 Subject: [PATCH 20/51] Added SERVER_NAME environment variable (#3110) Added SERVER_NAME environment variable to stop laravel framework server name defaulting to localhost, causing mail relays such as Gmail to stop silently dropping emails due to sender name being localhost. --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index 67d496d47..9e3ed6001 100644 --- a/.env.example +++ b/.env.example @@ -25,6 +25,11 @@ MAIL_USERNAME= MAIL_PASSWORD= MAIL_ENCRYPTION=tls MAIL_FROM=no-reply@example.com +# You should set this to your domain to prevent it defaulting to 'localhost', causing +# mail servers such as Gmail to reject your mail. +# +# @see: https://github.com/pterodactyl/panel/pull/3110 +# SERVER_NAME=panel.yourdomain.com QUEUE_HIGH=high QUEUE_STANDARD=standard From 38be4c9e4bda1702a9db62248f0b4f93810409bf Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 18 Apr 2021 00:05:31 +0300 Subject: [PATCH 21/51] More descriptive error for when backup limit is 0 (#3244) --- resources/scripts/components/server/backups/BackupContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index bd732d4f0..97dba20f8 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -60,7 +60,7 @@ const BackupContainer = () => { {backupLimit === 0 &&

- Backups cannot be created for this server. + Backups cannot be created for this server because the backup limit is set to 0.

} From 2d97b51628fc3e736775448ff336fd2bcfb390a4 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sat, 17 Apr 2021 18:57:37 -0400 Subject: [PATCH 22/51] Update issue template (#3271) Daemon was replaced with Wings, template updated to reflect that, Also askes for logs as they can be helpful, and reduce the amount of times we have to ask and wait for a reply. --- .github/ISSUE_TEMPLATE/---bug-report.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index f707992d2..4c5a1621a 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -1,31 +1,38 @@ --- name: "\U0001F41B Bug Report" about: For reporting code or design bugs with the software. DO NOT REPORT APACHE/NGINX/PHP CONFIGURATION ISSUES. - --- DO NOT REPORT ISSUES CONFIGURING: SSL, PHP, APACHE, NGINX, YOUR MACHINE, SSH, SFTP, ETC. ON THIS GITHUB TRACKER. -For assistance installing this software, as well as debugging issues with dependencies, please use our discord server: https://discord.gg/pterodactyl +For assistance installing this software, as well as debugging issues with dependencies, please use our discord server: You MUST complete all of the below information when reporting a bug, failure to do so will result in closure of your issue. PLEASE stop spamming our tracker with "bugs" that are not related to this project. +To obtain logs for the panel and wings the below commands should help with the retrevial of them. +Panel: ``` tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc bin.ptdl.co 99 ``` +Wings: ``` sudo wings diagnostics ``` + **STOP: READ FIRST, AND THEN DELETE THE ABOVE LINES** **Background (please complete the following information):** -* Panel or Daemon: -* Version of Panel/Daemon: -* Server's OS: -* Your Computer's OS & Browser: + +* Panel or Wings: +* Version of Panel/Wings: +* Panel Logs: +* Wings Logs: +* Server's OS: +* Your Computer's OS & Browser: **Describe the bug** A clear and concise description of what the bug is. Please provide additional information too, depending on what you have issues with: Panel: `php -v` (the php version in use). -Daemon: `uname -a` and `docker info` (your kernel version and information regarding docker) +Wings: `uname -a` and `docker info` (your kernel version and information regarding docker) **To Reproduce** -Steps to reproduce the behavior: +Steps to reproduce this behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' From b0995f645838174c258c5da39cd500f0d44ecd11 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sun, 18 Apr 2021 13:47:12 -0400 Subject: [PATCH 23/51] Update ---bug-report.md (#3272) Spelling correction, Removed code block as they're useless... --- .github/ISSUE_TEMPLATE/---bug-report.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index 4c5a1621a..14bced06c 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -5,13 +5,13 @@ about: For reporting code or design bugs with the software. DO NOT REPORT APACHE DO NOT REPORT ISSUES CONFIGURING: SSL, PHP, APACHE, NGINX, YOUR MACHINE, SSH, SFTP, ETC. ON THIS GITHUB TRACKER. -For assistance installing this software, as well as debugging issues with dependencies, please use our discord server: +For assistance installing this software, as well as debugging issues with dependencies, please use our discord server: https://discord.gg/pterodactyl -You MUST complete all of the below information when reporting a bug, failure to do so will result in closure of your issue. PLEASE stop spamming our tracker with "bugs" that are not related to this project. +You MUST complete all of the below information when reporting a bug, failure to do so will result in the closure of your issue. PLEASE stop spamming our tracker with "bugs" that are not related to this project. -To obtain logs for the panel and wings the below commands should help with the retrevial of them. -Panel: ``` tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc bin.ptdl.co 99 ``` -Wings: ``` sudo wings diagnostics ``` +To obtain logs for the panel and wings the below commands should help with the retrieval of them. +Panel: tail -n 100 /var/www/pterodactyl/storage/logs/laravel-$(date +%F).log | nc bin.ptdl.co 99 +Wings: sudo wings diagnostics **STOP: READ FIRST, AND THEN DELETE THE ABOVE LINES** From 3ca835e661a4902f9ba573c2f842f745ee94644c Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 20 Apr 2021 10:06:19 +0200 Subject: [PATCH 24/51] Add group input to upgrade command --- app/Console/Commands/UpgradeCommand.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index 6ea68b754..4508b41df 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -14,7 +14,7 @@ class UpgradeCommand extends Command /** @var string */ protected $signature = 'p:upgrade - {--user= : The user that PHP runs under. All files will be owned by this user.} + {--user= : The user (and group) that PHP runs under. All files will be owned by this user (and group).} {--url= : The specific archive to download.} {--release= : A specific Pterodactyl version to download from GitHub. Leave blank to use latest.} {--skip-download : If set no archive will be downloaded.}'; @@ -46,22 +46,26 @@ class UpgradeCommand extends Command } $user = 'www-data'; + $group = 'www-data'; if ($this->input->isInteractive()) { if (!$skipDownload) { $skipDownload = !$this->confirm('Would you like to download and unpack the archive files for the latest version?', true); } if (is_null($this->option('user'))) { - $details = posix_getpwuid(fileowner('public')); + $user_details = posix_getpwuid(fileowner('public')); $user = $details['name'] ?? 'www-data'; + + $group_details = posix_getgrgid(filegroup('public')); + $group = $details['name'] ?? 'www-data'; - if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) { + if (!$this->confirm("Your webserver user (and group) has been detected as [{$user}:{$group}]: is this correct?", true)) { $user = $this->anticipate( - 'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data", "nginx", or "apache".', + 'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data:www-data", "nginx:nginx", or "apache:apache".', [ - 'www-data', - 'apache', - 'nginx', + 'www-data:www-data', + 'nginx:nginx', + 'apache:apache', ] ); } @@ -136,9 +140,9 @@ class UpgradeCommand extends Command $this->call('migrate', ['--seed' => '', '--force' => '']); }); - $this->withProgress($bar, function () use ($user) { - $this->line("\$upgrader> chown -R {$user}:{$user} *"); - $process = Process::fromShellCommandline("chown -R {$user}:{$user} *", $this->getLaravel()->basePath()); + $this->withProgress($bar, function () use ($user, $group) { + $this->line("\$upgrader> chown -R {$user}:{$group} *"); + $process = Process::fromShellCommandline("chown -R {$user}:{$group} *", $this->getLaravel()->basePath()); $process->setTimeout(10 * 60); $process->run(function ($type, $buffer) { $this->{$type === Process::ERR ? 'error' : 'line'}($buffer); From 2f6351ec005ab9ec2f9f2c01ebcdb7aa3b69f9e9 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 20 Apr 2021 10:08:21 +0200 Subject: [PATCH 25/51] Small fix --- app/Console/Commands/UpgradeCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index 4508b41df..d13557b43 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -54,10 +54,10 @@ class UpgradeCommand extends Command if (is_null($this->option('user'))) { $user_details = posix_getpwuid(fileowner('public')); - $user = $details['name'] ?? 'www-data'; + $user = $user_details['name'] ?? 'www-data'; $group_details = posix_getgrgid(filegroup('public')); - $group = $details['name'] ?? 'www-data'; + $group = $group_details['name'] ?? 'www-data'; if (!$this->confirm("Your webserver user (and group) has been detected as [{$user}:{$group}]: is this correct?", true)) { $user = $this->anticipate( From c56e6999851aca5661c60fc08771e0560d0b9a69 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Tue, 20 Apr 2021 17:39:34 +0200 Subject: [PATCH 26/51] Separated user from group --- app/Console/Commands/UpgradeCommand.php | 36 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index d13557b43..db0e23fd4 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -14,7 +14,8 @@ class UpgradeCommand extends Command /** @var string */ protected $signature = 'p:upgrade - {--user= : The user (and group) that PHP runs under. All files will be owned by this user (and group).} + {--user= : The user that PHP runs under. All files will be owned by this user.} + {--group= : The group that PHP runs under. All files will be owned by this group.} {--url= : The specific archive to download.} {--release= : A specific Pterodactyl version to download from GitHub. Leave blank to use latest.} {--skip-download : If set no archive will be downloaded.}'; @@ -53,19 +54,32 @@ class UpgradeCommand extends Command } if (is_null($this->option('user'))) { - $user_details = posix_getpwuid(fileowner('public')); - $user = $user_details['name'] ?? 'www-data'; - - $group_details = posix_getgrgid(filegroup('public')); - $group = $group_details['name'] ?? 'www-data'; + $userDetails = posix_getpwuid(fileowner('public')); + $user = $userDetails['name'] ?? 'www-data'; - if (!$this->confirm("Your webserver user (and group) has been detected as [{$user}:{$group}]: is this correct?", true)) { + if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) { $user = $this->anticipate( - 'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data:www-data", "nginx:nginx", or "apache:apache".', + 'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data", "nginx", or "apache".', [ - 'www-data:www-data', - 'nginx:nginx', - 'apache:apache', + 'www-data', + 'nginx', + 'apache', + ] + ); + } + } + + if (is_null($this->option('group'))) { + $groupDetails = posix_getgrgid(filegroup('public')); + $group = $groupDetails['name'] ?? 'www-data'; + + if (!$this->confirm("Your webserver group has been detected as [{$group}]: is this correct?", true)) { + $group = $this->anticipate( + 'Please enter the name of the group running your webserver process. Normally this is the same as your user.', + [ + 'www-data', + 'nginx', + 'apache', ] ); } From db64f54010c0c7d938dedc781df3f6663e7da23a Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Wed, 21 Apr 2021 13:37:11 +1000 Subject: [PATCH 27/51] Drop explicit transaction from store_node_tokens_as_encrypted_value (#3280) Migrations are executed in transactions anyway, and creating a savepoint can cause spurious failures on databases that don't support transactional DDL (like MySQL and MariaDB) when it attempts to commit a savepoint that was silently not created because there wasn't an active transaction after some DDL was executed. While a better solution might involve splitting this migration into several so each one is only DDL or only data manipulation, I don't think that can be done very easily while maintaining compatibility with existing deployments. Fixes #3229. --- ...4_store_node_tokens_as_encrypted_value.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php index b3d94af89..6544679fe 100644 --- a/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php +++ b/database/migrations/2020_04_10_141024_store_node_tokens_as_encrypted_value.php @@ -33,19 +33,17 @@ class StoreNodeTokensAsEncryptedValue extends Migration $table->text('daemon_token')->change(); }); - DB::transaction(function () { - /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ - $encrypter = Container::getInstance()->make(Encrypter::class); + /** @var \Illuminate\Contracts\Encryption\Encrypter $encrypter */ + $encrypter = Container::getInstance()->make(Encrypter::class); - foreach (DB::select('SELECT id, daemon_token FROM nodes') as $datum) { - DB::update('UPDATE nodes SET uuid = ?, daemon_token_id = ?, daemon_token = ? WHERE id = ?', [ - Uuid::uuid4()->toString(), - substr($datum->daemon_token, 0, 16), - $encrypter->encrypt(substr($datum->daemon_token, 16)), - $datum->id, - ]); - } - }); + foreach (DB::select('SELECT id, daemon_token FROM nodes') as $datum) { + DB::update('UPDATE nodes SET uuid = ?, daemon_token_id = ?, daemon_token = ? WHERE id = ?', [ + Uuid::uuid4()->toString(), + substr($datum->daemon_token, 0, 16), + $encrypter->encrypt(substr($datum->daemon_token, 16)), + $datum->id, + ]); + } Schema::table('nodes', function (Blueprint $table) { $table->unique(['uuid']); From d2955b9361f535a9c8bfb7c277e6ff874ba8891d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 20 Apr 2021 20:41:08 -0700 Subject: [PATCH 28/51] Only request servers as an admin if actually an admin; closes #3242 --- resources/scripts/components/dashboard/DashboardContainer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index c04ca0820..7c9d266c2 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -25,8 +25,8 @@ export default () => { const [ showOnlyAdmin, setShowOnlyAdmin ] = usePersistedState(`${uuid}:show_all_servers`, false); const { data: servers, error } = useSWR>( - [ '/api/client/servers', showOnlyAdmin, page ], - () => getServers({ page, type: showOnlyAdmin ? 'admin' : undefined }), + [ '/api/client/servers', (showOnlyAdmin && rootAdmin), page ], + () => getServers({ page, type: (showOnlyAdmin && rootAdmin) ? 'admin' : undefined }), ); useEffect(() => { From 38a5f2dbbf57680e5519f5dbfa2098fd31efcae4 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 20 Apr 2021 20:48:40 -0700 Subject: [PATCH 29/51] Ensure allocations are persisted across page navigation correctly; closes #2729 --- .../components/server/network/NetworkContainer.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index fdc66aa6d..0bb6bd42a 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -11,6 +11,7 @@ import Can from '@/components/elements/Can'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import getServerAllocations from '@/api/swr/getServerAllocations'; import isEqual from 'react-fast-compare'; +import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect'; const NetworkContainer = () => { const [ loading, setLoading ] = useState(false); @@ -23,7 +24,7 @@ const NetworkContainer = () => { const { data, error, mutate } = getServerAllocations(); useEffect(() => { - mutate(allocations, false); + mutate(allocations); }, []); useEffect(() => { @@ -32,6 +33,12 @@ const NetworkContainer = () => { } }, [ error ]); + useDeepCompareEffect(() => { + if (!data) return; + + setServerFromState(state => ({ ...state, allocations: data })); + }, [ data ]); + const onCreateAllocation = () => { clearFlashes('server:network'); From 3858ca0807b4ec97323c4db3cf286a3e158315c7 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 22 Apr 2021 00:11:45 +0300 Subject: [PATCH 30/51] fix download url --- database/Seeders/eggs/minecraft/egg-forge-minecraft.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index 3793d7e55..7d3f18157 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -4,7 +4,7 @@ "version": "PTDL_v1", "update_url": null }, - "exported_at": "2021-03-15T18:04:38+02:00", + "exported_at": "2021-04-21T23:01:35+03:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -25,7 +25,7 @@ }, "scripts": { "installation": { - "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n FILE_SITE=$(echo -e ${JSON_DATA} | jq -r '.homepage' | sed \"s\/http:\/https:\/g\")\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar", + "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n\tFILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar", "container": "openjdk:8-jdk-slim", "entrypoint": "bash" } From 552b9d3c33d9528f02b25d23bd2fc5a2f86bc08e Mon Sep 17 00:00:00 2001 From: Julien Tant Date: Sat, 24 Apr 2021 15:06:21 -0700 Subject: [PATCH 31/51] Add possibility to run disabled cron --- .../Api/Client/Servers/ScheduleController.php | 4 ---- app/Jobs/Schedule/RunTaskJob.php | 14 ++++++++++---- app/Services/Schedules/ProcessScheduleService.php | 2 +- .../server/schedules/ScheduleEditContainer.tsx | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 320133595..5b2609e58 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -156,10 +156,6 @@ class ScheduleController extends ClientApiController */ public function execute(TriggerScheduleRequest $request, Server $server, Schedule $schedule) { - if (!$schedule->is_active) { - throw new BadRequestHttpException('Cannot trigger schedule exection for a schedule that is not currently active.'); - } - $this->service->handle($schedule, true); return new JsonResponse([], JsonResponse::HTTP_ACCEPTED); diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 3dd8e920d..b2b51578c 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -27,13 +27,19 @@ class RunTaskJob extends Job implements ShouldQueue */ public $task; + /** + * @var bool + */ + public $now; + /** * RunTaskJob constructor. */ - public function __construct(Task $task) + public function __construct(Task $task, $now = false) { $this->queue = config('pterodactyl.queues.standard'); $this->task = $task; + $this->now = $now; } /** @@ -46,8 +52,8 @@ class RunTaskJob extends Job implements ShouldQueue InitiateBackupService $backupService, DaemonPowerRepository $powerRepository ) { - // Do not process a task that is not set to active. - if (!$this->task->schedule->is_active) { + // Do not process a task that is not set to active, unless it's been trigger by the run now API. + if ($this->task->schedule->is_active && !$this->now) { $this->markTaskNotQueued(); $this->markScheduleComplete(); @@ -101,7 +107,7 @@ class RunTaskJob extends Job implements ShouldQueue $nextTask->update(['is_queued' => true]); - $this->dispatch((new self($nextTask))->delay($nextTask->time_offset)); + $this->dispatch((new self($nextTask, $this->now))->delay($nextTask->time_offset)); } /** diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index 23c9ea5e8..9e62d45cb 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -53,7 +53,7 @@ class ProcessScheduleService $task->update(['is_queued' => true]); }); - $job = new RunTaskJob($task); + $job = new RunTaskJob($task, $now); if (!$now) { $this->dispatcher->dispatch($job->delay($task->time_offset)); diff --git a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx index df0af5b99..60d7ca943 100644 --- a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx @@ -161,7 +161,7 @@ export default () => { onDeleted={() => history.push(`/server/${id}/schedules`)} /> - {schedule.isActive && schedule.tasks.length > 0 && + {schedule.tasks.length > 0 && From 6ef60633d3b73a1fe1d65b1fe54aa4d510f883e1 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 24 Apr 2021 16:39:56 -0700 Subject: [PATCH 32/51] Additional coverage to ensure values are wrapped as expected; ref #3287 --- .../Commands/EnvironmentWriterTrait.php | 30 ++++++------- .../Helpers/EnvironmentWriterTraitTest.php | 43 +++++++++++++++++++ 2 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 tests/Unit/Helpers/EnvironmentWriterTraitTest.php diff --git a/app/Traits/Commands/EnvironmentWriterTrait.php b/app/Traits/Commands/EnvironmentWriterTrait.php index 10692a9a4..5435dc321 100644 --- a/app/Traits/Commands/EnvironmentWriterTrait.php +++ b/app/Traits/Commands/EnvironmentWriterTrait.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Traits\Commands; @@ -13,6 +6,20 @@ use Pterodactyl\Exceptions\PterodactylException; trait EnvironmentWriterTrait { + /** + * Escapes an environment value by looking for any characters that could + * reasonablly cause environment parsing issues. Those values are then wrapped + * in quotes before being returned. + */ + public function escapeEnvironmentValue(string $value): string + { + if (!preg_match('/^\"(.*)\"$/', $value) && preg_match('/([^\w.\-+\/])+/', $value)) { + return sprintf('"%s"', addslashes($value)); + } + + return $value; + } + /** * Update the .env file for the application using the passed in values. * @@ -28,14 +35,7 @@ trait EnvironmentWriterTrait $saveContents = file_get_contents($path); collect($values)->each(function ($value, $key) use (&$saveContents) { $key = strtoupper($key); - // 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)); - } - - $saveValue = sprintf('%s=%s', $key, $value); + $saveValue = sprintf('%s=%s', $key, $this->escapeEnvironmentValue($value)); if (preg_match_all('/^' . $key . '=(.*)$/m', $saveContents) < 1) { $saveContents = $saveContents . PHP_EOL . $saveValue; diff --git a/tests/Unit/Helpers/EnvironmentWriterTraitTest.php b/tests/Unit/Helpers/EnvironmentWriterTraitTest.php new file mode 100644 index 000000000..f2022f798 --- /dev/null +++ b/tests/Unit/Helpers/EnvironmentWriterTraitTest.php @@ -0,0 +1,43 @@ +escapeEnvironmentValue($input); + + $this->assertSame($expected, $output); + } + + public function variableDataProvider(): array + { + return [ + ['foo', 'foo'], + ['abc123', 'abc123'], + ['val"ue', '"val\"ue"'], + ['my test value', '"my test value"'], + ['mysql_p@assword', '"mysql_p@assword"'], + ['mysql_p#assword', '"mysql_p#assword"'], + ['mysql p@$$word', '"mysql p@$$word"'], + ['mysql p%word', '"mysql p%word"'], + ['mysql p#word', '"mysql p#word"'], + ['abc_@#test', '"abc_@#test"'], + ['test 123 $$$', '"test 123 $$$"'], + ['#password%', '"#password%"'], + ['$pass ', '"$pass "'], + ]; + } +} + +class FooClass +{ + use EnvironmentWriterTrait; +} From d0c7e2c0e62f67ae3b78bc64c61d472bfeffc72f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 24 Apr 2021 16:45:54 -0700 Subject: [PATCH 33/51] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f070441be..dbf875146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.4.0 +### Fixed +* Removes the use of tagging when storing server resource usage in the cache. This addresses errors encountered when using the `file` driver. +* Fixes Wings response handling if Wings returns an error response with a 200-level status code that would improperly be passed back to the client as a successful request. +* Fixes use of JSON specific functions in SQL queries to better support MariaDB users. +* Fixes a migration that could fail on some MySQL/MariaDB setups when trying to encrypt node token values. + +### Changed +* Increases the maximum length allowed for a server name using the Rust egg. +* Updated server resource utilization API call to Wings to use new API response format used by `Wings@1.4.0`. + ## v1.3.2 ### Fixed * Fixes self-upgrade incorrectly executing the command to un-tar downloaded archives. From f7f972b33dac2885821ac5d551a0d0617df13f75 Mon Sep 17 00:00:00 2001 From: Julien Tant Date: Sat, 24 Apr 2021 18:18:29 -0700 Subject: [PATCH 34/51] rename now variable & fix condition --- app/Jobs/Schedule/RunTaskJob.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index b2b51578c..f60711f43 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -30,16 +30,16 @@ class RunTaskJob extends Job implements ShouldQueue /** * @var bool */ - public $now; + public $manualRun; /** * RunTaskJob constructor. */ - public function __construct(Task $task, $now = false) + public function __construct(Task $task, $manualRun = false) { $this->queue = config('pterodactyl.queues.standard'); $this->task = $task; - $this->now = $now; + $this->manualRun = $manualRun; } /** @@ -52,8 +52,8 @@ class RunTaskJob extends Job implements ShouldQueue InitiateBackupService $backupService, DaemonPowerRepository $powerRepository ) { - // Do not process a task that is not set to active, unless it's been trigger by the run now API. - if ($this->task->schedule->is_active && !$this->now) { + // Do not process a task that is not set to active, unless it's been manually triggered. + if (!$this->task->schedule->is_active && !$this->manualRun) { $this->markTaskNotQueued(); $this->markScheduleComplete(); @@ -107,7 +107,7 @@ class RunTaskJob extends Job implements ShouldQueue $nextTask->update(['is_queued' => true]); - $this->dispatch((new self($nextTask, $this->now))->delay($nextTask->time_offset)); + $this->dispatch((new self($nextTask, $this->manualRun))->delay($nextTask->time_offset)); } /** From b10f6184e043a5deb3a45bc7b607c149a728f3f5 Mon Sep 17 00:00:00 2001 From: Julien Tant Date: Sat, 24 Apr 2021 18:30:48 -0700 Subject: [PATCH 35/51] remove the test preventing disabled schedule to be manually executed --- .../Server/Schedule/ExecuteScheduleTest.php | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php index 7c9d34d18..9964691aa 100644 --- a/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/ExecuteScheduleTest.php @@ -51,26 +51,6 @@ class ExecuteScheduleTest extends ClientApiIntegrationTestCase }); } - /** - * Test that the schedule is not executed if it is not currently active. - */ - public function testScheduleIsNotExecutedIfNotActive() - { - [$user, $server] = $this->generateTestAccount(); - - /** @var \Pterodactyl\Models\Schedule $schedule */ - $schedule = Schedule::factory()->create([ - 'server_id' => $server->id, - 'is_active' => false, - ]); - - $response = $this->actingAs($user)->postJson($this->link($schedule, '/execute')); - - $response->assertStatus(Response::HTTP_BAD_REQUEST); - $response->assertJsonPath('errors.0.code', 'BadRequestHttpException'); - $response->assertJsonPath('errors.0.detail', 'Cannot trigger schedule exection for a schedule that is not currently active.'); - } - /** * Test that a user without the schedule update permission cannot execute it. */ From 92cd659db314d2755494dabbd0ad5637a08fe7fa Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 1 May 2021 10:44:40 -0700 Subject: [PATCH 36/51] Add underlying data changes necessary for new task & schedule features --- .../Api/Client/Servers/ScheduleController.php | 3 +- .../Client/Servers/ScheduleTaskController.php | 2 + app/Jobs/Schedule/RunTaskJob.php | 40 ++++-- app/Models/Schedule.php | 6 + app/Models/Task.php | 12 ++ .../Api/Client/ScheduleTransformer.php | 1 + .../Api/Client/TaskTransformer.php | 1 + ...dd_continue_on_failure_option_to_tasks.php | 32 +++++ ...when_server_online_option_to_schedules.php | 32 +++++ .../schedules/createOrUpdateSchedule.ts | 27 ++-- .../server/schedules/getServerSchedules.ts | 20 +-- .../scripts/components/elements/Field.tsx | 4 +- .../server/schedules/EditScheduleModal.tsx | 128 ++++++++---------- 13 files changed, 201 insertions(+), 107 deletions(-) create mode 100644 database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php create mode 100644 database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 5b2609e58..0bbc6bd73 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -16,7 +16,6 @@ use Pterodactyl\Services\Schedules\ProcessScheduleService; use Pterodactyl\Transformers\Api\Client\ScheduleTransformer; use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\ViewScheduleRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\StoreScheduleRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Schedules\DeleteScheduleRequest; @@ -81,6 +80,7 @@ class ScheduleController extends ClientApiController 'cron_hour' => $request->input('hour'), 'cron_minute' => $request->input('minute'), 'is_active' => (bool) $request->input('is_active'), + 'only_when_online' => (bool) $request->input('only_when_online'), 'next_run_at' => $this->getNextRunAt($request), ]); @@ -128,6 +128,7 @@ class ScheduleController extends ClientApiController 'cron_hour' => $request->input('hour'), 'cron_minute' => $request->input('minute'), 'is_active' => $active, + 'only_when_online' => (bool) $request->input('only_when_online'), 'next_run_at' => $this->getNextRunAt($request), ]; diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index a652071a2..12bcf968c 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -59,6 +59,7 @@ class ScheduleTaskController extends ClientApiController 'action' => $request->input('action'), 'payload' => $request->input('payload') ?? '', 'time_offset' => $request->input('time_offset'), + 'continue_on_failure' => (bool) $request->input('continue_on_failure'), ]); return $this->fractal->item($task) @@ -84,6 +85,7 @@ class ScheduleTaskController extends ClientApiController 'action' => $request->input('action'), 'payload' => $request->input('payload') ?? '', 'time_offset' => $request->input('time_offset'), + 'continue_on_failure' => (bool) $request->input('continue_on_failure'), ]); return $this->fractal->item($task->refresh()) diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index f60711f43..69a0d171f 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -7,6 +7,7 @@ use Pterodactyl\Jobs\Job; use Carbon\CarbonImmutable; use Pterodactyl\Models\Task; use InvalidArgumentException; +use Illuminate\Http\Response; use Pterodactyl\Models\Schedule; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; @@ -15,6 +16,7 @@ use Illuminate\Foundation\Bus\DispatchesJobs; use Pterodactyl\Services\Backups\InitiateBackupService; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; use Pterodactyl\Repositories\Wings\DaemonCommandRepository; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class RunTaskJob extends Job implements ShouldQueue { @@ -62,18 +64,32 @@ class RunTaskJob extends Job implements ShouldQueue $server = $this->task->server; // Perform the provided task against the daemon. - switch ($this->task->action) { - case 'power': - $powerRepository->setServer($server)->send($this->task->payload); - break; - case 'command': - $commandRepository->setServer($server)->send($this->task->payload); - break; - case 'backup': - $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); - break; - default: - throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); + try { + switch ($this->task->action) { + case Task::ACTION_POWER: + $powerRepository->setServer($server)->send($this->task->payload); + break; + case Task::ACTION_COMMAND: + $commandRepository->setServer($server)->send($this->task->payload); + break; + case Task::ACTION_BACKUP: + $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); + break; + default: + throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); + } + } catch (Exception $exception) { + if ($exception instanceof DaemonConnectionException) { + // If the task "failed" because the server is offline and it was sending a command or + // executing a power action (which shouldn't happen?) then just stop trying to process + // the schedule, but don't actually log the failure. + if ($this->task->action === Task::ACTION_POWER || $this->task->action === Task::ACTION_COMMAND) { + // Do the thing + if ($exception->getStatusCode() === Response::HTTP_CONFLICT) { + } + } + } + throw $exception; } $this->markTaskNotQueued(); diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index 240873cac..82a43c732 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -18,6 +18,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface; * @property string $cron_minute * @property bool $is_active * @property bool $is_processing + * @property bool $only_when_online * @property \Carbon\Carbon|null $last_run_at * @property \Carbon\Carbon|null $next_run_at * @property \Carbon\Carbon $created_at @@ -63,6 +64,7 @@ class Schedule extends Model 'cron_minute', 'is_active', 'is_processing', + 'only_when_online', 'last_run_at', 'next_run_at', ]; @@ -75,6 +77,7 @@ class Schedule extends Model 'server_id' => 'integer', 'is_active' => 'boolean', 'is_processing' => 'boolean', + 'only_when_online' => 'boolean', ]; /** @@ -99,6 +102,7 @@ class Schedule extends Model 'cron_minute' => '*', 'is_active' => true, 'is_processing' => false, + 'only_when_online' => false, ]; /** @@ -114,6 +118,7 @@ class Schedule extends Model 'cron_minute' => 'required|string', 'is_active' => 'boolean', 'is_processing' => 'boolean', + 'only_when_online' => 'boolean', 'last_run_at' => 'nullable|date', 'next_run_at' => 'nullable|date', ]; @@ -122,6 +127,7 @@ class Schedule extends Model * Returns the schedule's execution crontab entry as a string. * * @return \Carbon\CarbonImmutable + * * @throws \Exception */ public function getNextRunDate() diff --git a/app/Models/Task.php b/app/Models/Task.php index 84bd3cbf6..82ba72370 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -14,6 +14,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface; * @property string $payload * @property int $time_offset * @property bool $is_queued + * @property bool $continue_on_failure * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property string $hashid @@ -30,6 +31,13 @@ class Task extends Model */ public const RESOURCE_NAME = 'schedule_task'; + /** + * The default actions that can exist for a task in Pterodactyl. + */ + public const ACTION_POWER = 'power'; + public const ACTION_COMMAND = 'command'; + public const ACTION_BACKUP = 'backup'; + /** * The table associated with the model. * @@ -56,6 +64,7 @@ class Task extends Model 'payload', 'time_offset', 'is_queued', + 'continue_on_failure', ]; /** @@ -69,6 +78,7 @@ class Task extends Model 'sequence_id' => 'integer', 'time_offset' => 'integer', 'is_queued' => 'boolean', + 'continue_on_failure' => 'boolean', ]; /** @@ -79,6 +89,7 @@ class Task extends Model protected $attributes = [ 'time_offset' => 0, 'is_queued' => false, + 'continue_on_failure' => false, ]; /** @@ -91,6 +102,7 @@ class Task extends Model 'payload' => 'required_unless:action,backup|string', 'time_offset' => 'required|numeric|between:0,900', 'is_queued' => 'boolean', + 'continue_on_failure' => 'boolean', ]; /** diff --git a/app/Transformers/Api/Client/ScheduleTransformer.php b/app/Transformers/Api/Client/ScheduleTransformer.php index 44ea2c555..25ef9fb58 100644 --- a/app/Transformers/Api/Client/ScheduleTransformer.php +++ b/app/Transformers/Api/Client/ScheduleTransformer.php @@ -45,6 +45,7 @@ class ScheduleTransformer extends BaseClientTransformer ], 'is_active' => $model->is_active, 'is_processing' => $model->is_processing, + 'only_when_online' => $model->only_when_online, 'last_run_at' => $model->last_run_at ? $model->last_run_at->toIso8601String() : null, 'next_run_at' => $model->next_run_at ? $model->next_run_at->toIso8601String() : null, 'created_at' => $model->created_at->toIso8601String(), diff --git a/app/Transformers/Api/Client/TaskTransformer.php b/app/Transformers/Api/Client/TaskTransformer.php index 0b2956262..a2e62cf51 100644 --- a/app/Transformers/Api/Client/TaskTransformer.php +++ b/app/Transformers/Api/Client/TaskTransformer.php @@ -28,6 +28,7 @@ class TaskTransformer extends BaseClientTransformer 'payload' => $model->payload, 'time_offset' => $model->time_offset, 'is_queued' => $model->is_queued, + 'continue_on_failure' => $model->continue_on_failure, 'created_at' => $model->created_at->toIso8601String(), 'updated_at' => $model->updated_at->toIso8601String(), ]; diff --git a/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php b/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php new file mode 100644 index 000000000..4de4b59d4 --- /dev/null +++ b/database/migrations/2021_05_01_092457_add_continue_on_failure_option_to_tasks.php @@ -0,0 +1,32 @@ +unsignedTinyInteger('continue_on_failure')->after('is_queued')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropColumn('continue_on_failure'); + }); + } +} diff --git a/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php b/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php new file mode 100644 index 000000000..7fa9758ed --- /dev/null +++ b/database/migrations/2021_05_01_092523_add_only_run_when_server_online_option_to_schedules.php @@ -0,0 +1,32 @@ +unsignedTinyInteger('only_when_online')->after('is_processing')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('schedules', function (Blueprint $table) { + $table->dropColumn('only_when_online'); + }); + } +} diff --git a/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts b/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts index 481cea777..de1bc075b 100644 --- a/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts +++ b/resources/scripts/api/server/schedules/createOrUpdateSchedule.ts @@ -1,20 +1,19 @@ import { rawDataToServerSchedule, Schedule } from '@/api/server/schedules/getServerSchedules'; import http from '@/api/http'; -type Data = Pick & { id?: number } +type Data = Pick & { id?: number } -export default (uuid: string, schedule: Data): Promise => { - return new Promise((resolve, reject) => { - http.post(`/api/client/servers/${uuid}/schedules${schedule.id ? `/${schedule.id}` : ''}`, { - is_active: schedule.isActive, - name: schedule.name, - minute: schedule.cron.minute, - hour: schedule.cron.hour, - day_of_month: schedule.cron.dayOfMonth, - month: schedule.cron.month, - day_of_week: schedule.cron.dayOfWeek, - }) - .then(({ data }) => resolve(rawDataToServerSchedule(data.attributes))) - .catch(reject); +export default async (uuid: string, schedule: Data): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/schedules${schedule.id ? `/${schedule.id}` : ''}`, { + is_active: schedule.isActive, + only_when_online: schedule.onlyWhenOnline, + name: schedule.name, + minute: schedule.cron.minute, + hour: schedule.cron.hour, + day_of_month: schedule.cron.dayOfMonth, + month: schedule.cron.month, + day_of_week: schedule.cron.dayOfWeek, }); + + return rawDataToServerSchedule(data.attributes); }; diff --git a/resources/scripts/api/server/schedules/getServerSchedules.ts b/resources/scripts/api/server/schedules/getServerSchedules.ts index f7a07617c..c052e7a3c 100644 --- a/resources/scripts/api/server/schedules/getServerSchedules.ts +++ b/resources/scripts/api/server/schedules/getServerSchedules.ts @@ -12,6 +12,7 @@ export interface Schedule { }; isActive: boolean; isProcessing: boolean; + onlyWhenOnline: boolean; lastRunAt: Date | null; nextRunAt: Date | null; createdAt: Date; @@ -27,6 +28,7 @@ export interface Task { payload: string; timeOffset: number; isQueued: boolean; + continueOnFailure: boolean; createdAt: Date; updatedAt: Date; } @@ -38,6 +40,7 @@ export const rawDataToServerTask = (data: any): Task => ({ payload: data.payload, timeOffset: data.time_offset, isQueued: data.is_queued, + continueOnFailure: data.continue_on_failure, createdAt: new Date(data.created_at), updatedAt: new Date(data.updated_at), }); @@ -54,6 +57,7 @@ export const rawDataToServerSchedule = (data: any): Schedule => ({ }, isActive: data.is_active, isProcessing: data.is_processing, + onlyWhenOnline: data.only_when_online, lastRunAt: data.last_run_at ? new Date(data.last_run_at) : null, nextRunAt: data.next_run_at ? new Date(data.next_run_at) : null, createdAt: new Date(data.created_at), @@ -62,14 +66,12 @@ export const rawDataToServerSchedule = (data: any): Schedule => ({ tasks: (data.relationships?.tasks?.data || []).map((row: any) => rawDataToServerTask(row.attributes)), }); -export default (uuid: string): Promise => { - return new Promise((resolve, reject) => { - http.get(`/api/client/servers/${uuid}/schedules`, { - params: { - include: [ 'tasks' ], - }, - }) - .then(({ data }) => resolve((data.data || []).map((row: any) => rawDataToServerSchedule(row.attributes)))) - .catch(reject); +export default async (uuid: string): Promise => { + const { data } = await http.get(`/api/client/servers/${uuid}/schedules`, { + params: { + include: [ 'tasks' ], + }, }); + + return (data.data || []).map((row: any) => rawDataToServerSchedule(row.attributes)); }; diff --git a/resources/scripts/components/elements/Field.tsx b/resources/scripts/components/elements/Field.tsx index 0c76f285c..e7ea16679 100644 --- a/resources/scripts/components/elements/Field.tsx +++ b/resources/scripts/components/elements/Field.tsx @@ -17,7 +17,7 @@ const Field = forwardRef(({ id, name, light = false, la { ({ field, form: { errors, touched } }: FieldProps) => ( - <> +
{label && } @@ -35,7 +35,7 @@ const Field = forwardRef(({ id, name, light = false, la : description ?

{description}

: null } - +
) }
diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index e0df9e981..3db483244 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Schedule } from '@/api/server/schedules/getServerSchedules'; -import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import Field from '@/components/elements/Field'; -import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Form, Formik, FormikHelpers } from 'formik'; import FormikSwitch from '@/components/elements/FormikSwitch'; import createOrUpdateSchedule from '@/api/server/schedules/createOrUpdateSchedule'; import { ServerContext } from '@/state/server'; @@ -11,10 +10,12 @@ import FlashMessageRender from '@/components/FlashMessageRender'; import useFlash from '@/plugins/useFlash'; import tw from 'twin.macro'; import Button from '@/components/elements/Button'; +import ModalContext from '@/context/ModalContext'; +import asModal from '@/hoc/asModal'; -type Props = { +interface Props { schedule?: Schedule; -} & RequiredModalProps; +} interface Values { name: string; @@ -24,70 +25,21 @@ interface Values { hour: string; minute: string; enabled: boolean; + onlyWhenOnline: boolean; } -const EditScheduleModal = ({ schedule, ...props }: Omit) => { - const { isSubmitting } = useFormikContext(); - - return ( - -

{schedule ? 'Edit schedule' : 'Create new schedule'}

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

- The schedule system supports the use of Cronjob syntax when defining when tasks should begin - running. Use the fields above to specify when these tasks should begin running. -

-
- -
-
- -
- -
- ); -}; - -export default ({ schedule, visible, ...props }: Props) => { - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); +const EditScheduleModal = ({ schedule }: Props) => { const { addError, clearFlashes } = useFlash(); - const [ modalVisible, setModalVisible ] = useState(visible); + const { dismiss } = useContext(ModalContext); + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); useEffect(() => { - setModalVisible(visible); - clearFlashes('schedule:edit'); - }, [ visible ]); + return () => { + clearFlashes('schedule:edit'); + }; + }, []); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('schedule:edit'); @@ -101,12 +53,13 @@ export default ({ schedule, visible, ...props }: Props) => { month: values.month, dayOfMonth: values.dayOfMonth, }, + onlyWhenOnline: values.onlyWhenOnline, isActive: values.enabled, }) .then(schedule => { setSubmitting(false); appendSchedule(schedule); - setModalVisible(false); + dismiss(); }) .catch(error => { console.error(error); @@ -128,13 +81,50 @@ export default ({ schedule, visible, ...props }: Props) => { dayOfWeek: schedule?.cron.dayOfWeek || '*', enabled: schedule ? schedule.isActive : true, } as Values} - validationSchema={null} > - + {({ isSubmitting }) => ( +
+

{schedule ? 'Edit schedule' : 'Create new schedule'}

+ + +
+ + + + + +
+

+ The schedule system supports the use of Cronjob syntax when defining when tasks should begin + running. Use the fields above to specify when these tasks should begin running. +

+
+ +
+
+ +
+
+ +
+ + )} ); }; + +export default asModal()(EditScheduleModal); From ea057cb1cb2311e0ac20f9f092da542ac2ed3f56 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 1 May 2021 11:24:18 -0700 Subject: [PATCH 37/51] Update UI to support setting "Continue on Error" for tasks --- .../schedules/createOrUpdateScheduleTask.ts | 17 +- .../server/schedules/EditScheduleModal.tsx | 6 +- .../server/schedules/NewTaskButton.tsx | 7 +- .../server/schedules/ScheduleContainer.tsx | 2 +- .../schedules/ScheduleEditContainer.tsx | 2 +- .../server/schedules/ScheduleTaskRow.tsx | 25 +- .../server/schedules/TaskDetailsModal.tsx | 214 +++++++++--------- 7 files changed, 149 insertions(+), 124 deletions(-) diff --git a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts index c0d7fbbe7..c2bfc807b 100644 --- a/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts +++ b/resources/scripts/api/server/schedules/createOrUpdateScheduleTask.ts @@ -5,15 +5,16 @@ interface Data { action: string; payload: string; timeOffset: string | number; + continueOnFailure: boolean; } -export default (uuid: string, schedule: number, task: number | undefined, { timeOffset, ...data }: Data): Promise => { - return new Promise((resolve, reject) => { - http.post(`/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, { - ...data, - time_offset: timeOffset, - }) - .then(({ data }) => resolve(rawDataToServerTask(data.attributes))) - .catch(reject); +export default async (uuid: string, schedule: number, task: number | undefined, data: Data): Promise => { + const { data: response } = await http.post(`/api/client/servers/${uuid}/schedules/${schedule}/tasks${task ? `/${task}` : ''}`, { + action: data.action, + payload: data.payload, + continue_on_failure: data.continueOnFailure, + time_offset: data.timeOffset, }); + + return rawDataToServerTask(response.attributes); }; diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index 3db483244..e00cef04a 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -104,15 +104,15 @@ const EditScheduleModal = ({ schedule }: Props) => {

diff --git a/resources/scripts/components/server/schedules/NewTaskButton.tsx b/resources/scripts/components/server/schedules/NewTaskButton.tsx index 9234f5b42..2ac7b276c 100644 --- a/resources/scripts/components/server/schedules/NewTaskButton.tsx +++ b/resources/scripts/components/server/schedules/NewTaskButton.tsx @@ -13,12 +13,7 @@ export default ({ schedule }: Props) => { return ( <> - {visible && - setVisible(false)} - /> - } + setVisible(false)}/> diff --git a/resources/scripts/components/server/schedules/ScheduleContainer.tsx b/resources/scripts/components/server/schedules/ScheduleContainer.tsx index ddcd0a865..85e6db20d 100644 --- a/resources/scripts/components/server/schedules/ScheduleContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleContainer.tsx @@ -67,7 +67,7 @@ export default () => { }
- {visible && setVisible(false)}/>} + setVisible(false)}/> diff --git a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx index 60d7ca943..a643d2a54 100644 --- a/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx +++ b/resources/scripts/components/server/schedules/ScheduleEditContainer.tsx @@ -153,7 +153,7 @@ export default () => { }
- +
{ return (
- {isEditing && setIsEditing(false)} - />} + visible={isEditing} + onModalDismissed={() => setIsEditing(false)} + /> { }
+ {task.continueOnFailure && +
+
+ + Continues on Failure +
+
+ } {task.sequenceId > 1 && task.timeOffset > 0 &&
diff --git a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx index 08f33b161..9b30d66fa 100644 --- a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx +++ b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx @@ -1,13 +1,12 @@ -import React, { useEffect } from 'react'; -import Modal from '@/components/elements/Modal'; +import React, { useContext, useEffect } from 'react'; import { Schedule, Task } from '@/api/server/schedules/getServerSchedules'; -import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { Field as FormikField, Form, Formik, FormikHelpers, useField } from 'formik'; import { ServerContext } from '@/state/server'; import createOrUpdateScheduleTask from '@/api/server/schedules/createOrUpdateScheduleTask'; import { httpErrorToHuman } from '@/api/http'; import Field from '@/components/elements/Field'; import FlashMessageRender from '@/components/FlashMessageRender'; -import { number, object, string } from 'yup'; +import { boolean, number, object, string } from 'yup'; import useFlash from '@/plugins/useFlash'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; import tw from 'twin.macro'; @@ -15,105 +14,66 @@ import Label from '@/components/elements/Label'; import { Textarea } from '@/components/elements/Input'; import Button from '@/components/elements/Button'; import Select from '@/components/elements/Select'; +import ModalContext from '@/context/ModalContext'; +import asModal from '@/hoc/asModal'; +import FormikSwitch from '@/components/elements/FormikSwitch'; interface Props { schedule: Schedule; // If a task is provided we can assume we're editing it. If not provided, // we are creating a new one. task?: Task; - onDismissed: () => void; } interface Values { action: string; payload: string; timeOffset: string; + continueOnFailure: boolean; } -const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => { - const { values: { action }, initialValues, setFieldValue, setFieldTouched, isSubmitting } = useFormikContext(); +const schema = object().shape({ + action: string().required().oneOf([ 'command', 'power', 'backup' ]), + payload: string().when('action', { + is: v => v !== 'backup', + then: string().required('A task payload must be provided.'), + otherwise: string(), + }), + continueOnFailure: boolean(), + timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.') + .required('A time offset value must be provided.') + .min(0, 'The time offset must be at least 0 seconds.') + .max(900, 'The time offset must be less than 900 seconds.'), +}); + +const ActionListener = () => { + const [ { value }, { initialValue: initialAction } ] = useField('action'); + const [ , { initialValue: initialPayload }, { setValue, setTouched } ] = useField('payload'); useEffect(() => { - if (action !== initialValues.action) { - setFieldValue('payload', action === 'power' ? 'start' : ''); - setFieldTouched('payload', false); + if (value !== initialAction) { + setValue(value === 'power' ? 'start' : ''); + setTouched(false); } else { - setFieldValue('payload', initialValues.payload); - setFieldTouched('payload', false); + setValue(initialPayload || ''); + setTouched(false); } - }, [ action ]); + }, [ value ]); - return ( -
-

{isEditingTask ? 'Edit Task' : 'Create Task'}

-
-
- - - - - - - - -
-
- -
-
-
- {action === 'command' ? -
- - - - -
- : - action === 'power' ? -
- - - - - - - - - -
- : -
- - - - -
- } -
-
- -
-
- ); + return null; }; -export default ({ task, schedule, onDismissed }: Props) => { - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); +const TaskDetailsModal = ({ schedule, task }: Props) => { + const { dismiss } = useContext(ModalContext); const { clearFlashes, addError } = useFlash(); + + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); useEffect(() => { - clearFlashes('schedule:task'); + return () => { + clearFlashes('schedule:task'); + }; }, []); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { @@ -126,7 +86,7 @@ export default ({ task, schedule, onDismissed }: Props) => { } appendSchedule({ ...schedule, tasks }); - onDismissed(); + dismiss(); }) .catch(error => { console.error(error); @@ -138,35 +98,87 @@ export default ({ task, schedule, onDismissed }: Props) => { return ( v !== 'backup', - then: string().required('A task payload must be provided.'), - otherwise: string(), - }), - timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.') - .required('A time offset value must be provided.') - .min(0, 'The time offset must be at least 0 seconds.') - .max(900, 'The time offset must be less than 900 seconds.'), - })} > - {({ isSubmitting }) => ( - onDismissed()} - showSpinnerOverlay={isSubmitting} - > - - - + {({ isSubmitting, values }) => ( +
+ +

{task ? 'Edit Task' : 'Create Task'}

+
+
+ + + + + + + + + +
+
+ +
+
+
+ {values.action === 'command' ? +
+ + + + +
+ : + values.action === 'power' ? +
+ + + + + + + + + +
+ : +
+ + + + +
+ } +
+
+ +
+
+ +
+ )}
); }; + +export default asModal()(TaskDetailsModal); From 84483d36ee6433b418487163c16891b97ad782b0 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 1 May 2021 11:25:35 -0700 Subject: [PATCH 38/51] Fix use of onlyWhenOnline --- .../scripts/components/server/schedules/EditScheduleModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index e00cef04a..57602f2ed 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -79,7 +79,8 @@ const EditScheduleModal = ({ schedule }: Props) => { dayOfMonth: schedule?.cron.dayOfMonth || '*', month: schedule?.cron.month || '*', dayOfWeek: schedule?.cron.dayOfWeek || '*', - enabled: schedule ? schedule.isActive : true, + enabled: schedule?.isActive ?? true, + onlyWhenOnline: schedule?.onlyWhenOnline ?? true, } as Values} > {({ isSubmitting }) => ( From 7a85c315532808053cf047e5058ec8f2271b10a6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 1 May 2021 11:52:02 -0700 Subject: [PATCH 39/51] Add internal code support for stopping tasks if server is not running or continuing through on task error --- app/Jobs/Schedule/RunTaskJob.php | 16 +++------ .../Schedules/ProcessScheduleService.php | 36 +++++++++++++++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 69a0d171f..ddedb0260 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -7,8 +7,6 @@ use Pterodactyl\Jobs\Job; use Carbon\CarbonImmutable; use Pterodactyl\Models\Task; use InvalidArgumentException; -use Illuminate\Http\Response; -use Pterodactyl\Models\Schedule; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; @@ -79,17 +77,11 @@ class RunTaskJob extends Job implements ShouldQueue throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); } } catch (Exception $exception) { - if ($exception instanceof DaemonConnectionException) { - // If the task "failed" because the server is offline and it was sending a command or - // executing a power action (which shouldn't happen?) then just stop trying to process - // the schedule, but don't actually log the failure. - if ($this->task->action === Task::ACTION_POWER || $this->task->action === Task::ACTION_COMMAND) { - // Do the thing - if ($exception->getStatusCode() === Response::HTTP_CONFLICT) { - } - } + // If this isn't a DaemonConnectionException on a task that allows for failures + // throw the exception back up the chain so that the task is stopped. + if (!($this->task->continue_on_failure && $exception instanceof DaemonConnectionException)) { + throw $exception; } - throw $exception; } $this->markTaskNotQueued(); diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index 9e62d45cb..a131ad573 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -8,6 +8,8 @@ use Illuminate\Contracts\Bus\Dispatcher; use Pterodactyl\Jobs\Schedule\RunTaskJob; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Repositories\Wings\DaemonServerRepository; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class ProcessScheduleService { @@ -21,13 +23,19 @@ class ProcessScheduleService */ private $connection; + /** + * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository + */ + private $serverRepository; + /** * ProcessScheduleService constructor. */ - public function __construct(ConnectionInterface $connection, Dispatcher $dispatcher) + public function __construct(ConnectionInterface $connection, DaemonServerRepository $serverRepository, Dispatcher $dispatcher) { $this->dispatcher = $dispatcher; $this->connection = $connection; + $this->serverRepository = $serverRepository; } /** @@ -38,7 +46,7 @@ class ProcessScheduleService public function handle(Schedule $schedule, bool $now = false) { /** @var \Pterodactyl\Models\Task $task */ - $task = $schedule->tasks()->orderBy('sequence_id', 'asc')->first(); + $task = $schedule->tasks()->orderBy('sequence_id')->first(); if (is_null($task)) { throw new DisplayException('Cannot process schedule for task execution: no tasks are registered.'); @@ -54,6 +62,30 @@ class ProcessScheduleService }); $job = new RunTaskJob($task, $now); + if ($schedule->only_when_online) { + // Check that the server is currently in a starting or running state before executing + // this schedule if this option has been set. + try { + $details = $this->serverRepository->setServer($schedule->server)->getDetails(); + $state = $details['state'] ?? 'offline'; + // If the server is stopping or offline just do nothing with this task. + if (in_array($state, ['offline', 'stopping'])) { + $job->failed(); + + return; + } + } catch (Exception $exception) { + if (!$exception instanceof DaemonConnectionException) { + // If we encountered some exception during this process that wasn't just an + // issue connecting to Wings run the failed sequence for a job. Otherwise we + // can just quietly mark the task as completed without actually running anything. + $job->failed($exception); + } + $job->failed(); + + return; + } + } if (!$now) { $this->dispatcher->dispatch($job->delay($task->time_offset)); From 8ab3ad3f1aba59b71b4085ac7eaa0c49b261d08f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 1 May 2021 11:54:23 -0700 Subject: [PATCH 40/51] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf875146..879ec3bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.4.1 +### Added +* Adds support for only running a schedule if the server is currently in an online state. +* Adds support for ignoring errors during task execution and continuing on to the next item in the sequence. For example, continuing to a server restart even if sending a command beforehand failed. +* Adds the ability to specify the group to use for file permissions when using the `p:upgrade` command. +* Adds the ability to manually run a schedule even if it is currently disabled. + ## v1.4.0 ### Fixed * Removes the use of tagging when storing server resource usage in the cache. This addresses errors encountered when using the `file` driver. From 5f48712c28ebd2d3726d1235b33d336fd2c0cf3a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 1 May 2021 12:24:42 -0700 Subject: [PATCH 41/51] Add test coverage for RunTaskJob --- app/Jobs/Schedule/RunTaskJob.php | 2 +- .../Jobs/Schedule/RunTaskJobTest.php | 157 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/Jobs/Schedule/RunTaskJobTest.php diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index ddedb0260..0cffe4e85 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -74,7 +74,7 @@ class RunTaskJob extends Job implements ShouldQueue $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); break; default: - throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); + throw new InvalidArgumentException('Invalid task action provided: ' . $this->task->action); } } catch (Exception $exception) { // If this isn't a DaemonConnectionException on a task that allows for failures diff --git a/tests/Integration/Jobs/Schedule/RunTaskJobTest.php b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php new file mode 100644 index 000000000..bd943cbc9 --- /dev/null +++ b/tests/Integration/Jobs/Schedule/RunTaskJobTest.php @@ -0,0 +1,157 @@ +createServerModel(); + + /** @var \Pterodactyl\Models\Schedule $schedule */ + $schedule = Schedule::factory()->create([ + 'server_id' => $server->id, + 'is_processing' => true, + 'last_run_at' => null, + 'is_active' => false, + ]); + /** @var \Pterodactyl\Models\Task $task */ + $task = Task::factory()->create(['schedule_id' => $schedule->id, 'is_queued' => true]); + + $job = new RunTaskJob($task); + + Bus::dispatchNow($job); + + $task->refresh(); + $schedule->refresh(); + + $this->assertFalse($task->is_queued); + $this->assertFalse($schedule->is_processing); + $this->assertFalse($schedule->is_active); + $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + } + + public function testJobWithInvalidActionThrowsException() + { + $server = $this->createServerModel(); + + /** @var \Pterodactyl\Models\Schedule $schedule */ + $schedule = Schedule::factory()->create(['server_id' => $server->id]); + /** @var \Pterodactyl\Models\Task $task */ + $task = Task::factory()->create(['schedule_id' => $schedule->id, 'action' => 'foobar']); + + $job = new RunTaskJob($task); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid task action provided: foobar'); + Bus::dispatchNow($job); + } + + /** + * @dataProvider isManualRunDataProvider + */ + public function testJobIsExecuted(bool $isManualRun) + { + $server = $this->createServerModel(); + + /** @var \Pterodactyl\Models\Schedule $schedule */ + $schedule = Schedule::factory()->create([ + 'server_id' => $server->id, + 'is_active' => !$isManualRun, + 'is_processing' => true, + 'last_run_at' => null, + ]); + /** @var \Pterodactyl\Models\Task $task */ + $task = Task::factory()->create([ + 'schedule_id' => $schedule->id, + 'action' => Task::ACTION_POWER, + 'payload' => 'start', + 'is_queued' => true, + 'continue_on_failure' => false, + ]); + + $mock = Mockery::mock(DaemonPowerRepository::class); + $this->instance(DaemonPowerRepository::class, $mock); + + $mock->expects('setServer')->with(Mockery::on(function ($value) use ($server) { + return $value instanceof Server && $value->id === $server->id; + }))->andReturnSelf(); + $mock->expects('send')->with('start')->andReturn(new Response()); + + Bus::dispatchNow(new RunTaskJob($task, $isManualRun)); + + $task->refresh(); + $schedule->refresh(); + + $this->assertFalse($task->is_queued); + $this->assertFalse($schedule->is_processing); + $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + } + + /** + * @dataProvider isManualRunDataProvider + */ + public function testExceptionDuringRunIsHandledCorrectly(bool $continueOnFailure) + { + $server = $this->createServerModel(); + + /** @var \Pterodactyl\Models\Schedule $schedule */ + $schedule = Schedule::factory()->create(['server_id' => $server->id]); + /** @var \Pterodactyl\Models\Task $task */ + $task = Task::factory()->create([ + 'schedule_id' => $schedule->id, + 'action' => Task::ACTION_POWER, + 'payload' => 'start', + 'continue_on_failure' => $continueOnFailure, + ]); + + $mock = Mockery::mock(DaemonPowerRepository::class); + $this->instance(DaemonPowerRepository::class, $mock); + + $mock->expects('setServer->send')->andThrow( + new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response())) + ); + + if (!$continueOnFailure) { + $this->expectException(DaemonConnectionException::class); + } + + Bus::dispatchNow(new RunTaskJob($task)); + + if ($continueOnFailure) { + $task->refresh(); + $schedule->refresh(); + + $this->assertFalse($task->is_queued); + $this->assertFalse($schedule->is_processing); + $this->assertTrue(CarbonImmutable::now()->isSameAs(CarbonImmutable::ISO8601, $schedule->last_run_at)); + } + } + + /** + * @return array + */ + public function isManualRunDataProvider() + { + return [[true], [false]]; + } +} From 5d5e4ca7b1e46d005785c1899678c3b7f0257004 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 3 May 2021 21:26:09 -0700 Subject: [PATCH 42/51] Add support for locking backups to prevent any accidental deletions --- .../Service/Backup/BackupLockedException.php | 16 +++++ .../Api/Client/Servers/BackupController.php | 47 ++++++++++--- .../Servers/Backups/StoreBackupRequest.php | 1 + app/Models/AuditLog.php | 24 ++++--- app/Models/Backup.php | 4 ++ app/Services/Backups/DeleteBackupService.php | 5 ++ .../Backups/InitiateBackupService.php | 48 ++++++++++--- .../Api/Client/BackupTransformer.php | 1 + ...01016_add_support_for_locking_a_backup.php | 32 +++++++++ .../api/server/backups/createServerBackup.ts | 20 ++++-- resources/scripts/api/server/types.d.ts | 1 + resources/scripts/api/transformers.ts | 1 + .../server/backups/BackupContextMenu.tsx | 68 ++++++++++++++----- .../components/server/backups/BackupRow.tsx | 9 ++- .../server/backups/ChecksumModal.tsx | 17 ----- .../server/backups/CreateBackupButton.tsx | 37 ++++++---- resources/scripts/hoc/asModal.tsx | 6 +- routes/api-client.php | 1 + 18 files changed, 250 insertions(+), 88 deletions(-) create mode 100644 app/Exceptions/Service/Backup/BackupLockedException.php create mode 100644 database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php delete mode 100644 resources/scripts/components/server/backups/ChecksumModal.tsx diff --git a/app/Exceptions/Service/Backup/BackupLockedException.php b/app/Exceptions/Service/Backup/BackupLockedException.php new file mode 100644 index 000000000..3c9dbcf89 --- /dev/null +++ b/app/Exceptions/Service/Backup/BackupLockedException.php @@ -0,0 +1,16 @@ +audit(AuditLog::SERVER__BACKUP_STARTED, function (AuditLog $model, Server $server) use ($request) { - $backup = $this->initiateBackupService - ->setIgnoredFiles( - explode(PHP_EOL, $request->input('ignored') ?? '') - ) - ->handle($server, $request->input('name')); + $action = $this->initiateBackupService + ->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? '')); + + // Only set the lock status if the user even has permission to delete backups, + // otherwise ignore this status. This gets a little funky since it isn't clear + // how best to allow a user to create a backup that is locked without also preventing + // them from just filling up a server with backups that can never be deleted? + if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + $action->setIsLocked((bool) $request->input('is_locked')); + } + + $backup = $action->handle($server, $request->input('name')); $model->metadata = ['backup_uuid' => $backup->uuid]; @@ -105,11 +110,35 @@ class BackupController extends ClientApiController ->toArray(); } + /** + * Toggles the lock status of a given backup for a server. + * + * @throws \Throwable + * @throws \Illuminate\Auth\Access\AuthorizationException + */ + public function toggleLock(Request $request, Server $server, Backup $backup): array + { + if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) { + throw new AuthorizationException(); + } + + $action = $backup->is_locked ? AuditLog::SERVER__BACKUP_UNLOCKED : AuditLog::SERVER__BACKUP_LOCKED; + $server->audit($action, function (AuditLog $audit) use ($backup) { + $audit->metadata = ['backup_uuid' => $backup->uuid]; + + $backup->update(['is_locked' => !$backup->is_locked]); + }); + + $backup->refresh(); + + return $this->fractal->item($backup) + ->transformWith($this->getTransformer(BackupTransformer::class)) + ->toArray(); + } + /** * Returns information about a single backup. * - * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation - * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified * @throws \Illuminate\Auth\Access\AuthorizationException */ public function view(Request $request, Server $server, Backup $backup): array diff --git a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php index 538588ab0..5fbdaf728 100644 --- a/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Backups/StoreBackupRequest.php @@ -19,6 +19,7 @@ class StoreBackupRequest extends ClientApiRequest { return [ 'name' => 'nullable|string|max:191', + 'is_locked' => 'nullable|boolean', 'ignored' => 'nullable|string', ]; } diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index 88e8c1d8d..eee50d694 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -7,17 +7,17 @@ use Illuminate\Http\Request; use Illuminate\Container\Container; /** - * @property int $id - * @property string $uuid - * @property bool $is_system - * @property int|null $user_id - * @property int|null $server_id - * @property string $action - * @property string|null $subaction - * @property array $device - * @property array $metadata - * @property \Carbon\CarbonImmutable $created_at - * @property \Pterodactyl\Models\User|null $user + * @property int $id + * @property string $uuid + * @property bool $is_system + * @property int|null $user_id + * @property int|null $server_id + * @property string $action + * @property string|null $subaction + * @property array $device + * @property array $metadata + * @property \Carbon\CarbonImmutable $created_at + * @property \Pterodactyl\Models\User|null $user * @property \Pterodactyl\Models\Server|null $server */ class AuditLog extends Model @@ -36,6 +36,8 @@ class AuditLog extends Model public const SERVER__BACKUP_COMPELTED = 'server:backup.completed'; public const SERVER__BACKUP_DELETED = 'server:backup.deleted'; public const SERVER__BACKUP_DOWNLOADED = 'server:backup.downloaded'; + public const SERVER__BACKUP_LOCKED = 'server:backup.locked'; + public const SERVER__BACKUP_UNLOCKED = 'server:backup.unlocked'; public const SERVER__BACKUP_RESTORE_STARTED = 'server:backup.restore.started'; public const SERVER__BACKUP_RESTORE_COMPLETED = 'server:backup.restore.completed'; public const SERVER__BACKUP_RESTORE_FAILED = 'server:backup.restore.failed'; diff --git a/app/Models/Backup.php b/app/Models/Backup.php index c2f9e5d96..3f791abf4 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int $server_id * @property string $uuid * @property bool $is_successful + * @property bool $is_locked * @property string $name * @property string[] $ignored_files * @property string $disk @@ -46,6 +47,7 @@ class Backup extends Model protected $casts = [ 'id' => 'int', 'is_successful' => 'bool', + 'is_locked' => 'bool', 'ignored_files' => 'array', 'bytes' => 'int', ]; @@ -62,6 +64,7 @@ class Backup extends Model */ protected $attributes = [ 'is_successful' => true, + 'is_locked' => false, 'checksum' => null, 'bytes' => 0, 'upload_id' => null, @@ -79,6 +82,7 @@ class Backup extends Model 'server_id' => 'bail|required|numeric|exists:servers,id', 'uuid' => 'required|uuid', 'is_successful' => 'boolean', + 'is_locked' => 'boolean', 'name' => 'required|string', 'ignored_files' => 'array', 'disk' => 'required|string', diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php index 6c612e048..772392e21 100644 --- a/app/Services/Backups/DeleteBackupService.php +++ b/app/Services/Backups/DeleteBackupService.php @@ -9,6 +9,7 @@ use Illuminate\Database\ConnectionInterface; use Pterodactyl\Extensions\Backups\BackupManager; use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; +use Pterodactyl\Exceptions\Service\Backup\BackupLockedException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class DeleteBackupService @@ -55,6 +56,10 @@ class DeleteBackupService */ public function handle(Backup $backup) { + if ($backup->is_locked) { + throw new BackupLockedException(); + } + if ($backup->disk === Backup::ADAPTER_AWS_S3) { $this->deleteFromS3($backup); diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index b55f0f8ce..347740dc1 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -21,6 +21,11 @@ class InitiateBackupService */ private $ignoredFiles; + /** + * @var bool + */ + private $isLocked = false; + /** * @var \Pterodactyl\Repositories\Eloquent\BackupRepository */ @@ -49,7 +54,11 @@ class InitiateBackupService /** * InitiateBackupService constructor. * + * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository * @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService + * @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager */ public function __construct( BackupRepository $repository, @@ -65,6 +74,19 @@ class InitiateBackupService $this->deleteBackupService = $deleteBackupService; } + /** + * Set if the backup should be locked once it is created which will prevent + * its deletion by users or automated system processes. + * + * @return $this + */ + public function setIsLocked(bool $isLocked): self + { + $this->isLocked = $isLocked; + + return $this; + } + /** * Sets the files to be ignored by this backup. * @@ -91,7 +113,7 @@ class InitiateBackupService } /** - * Initiates the backup process for a server on the daemon. + * Initiates the backup process for a server on Wings. * * @throws \Throwable * @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException @@ -104,23 +126,30 @@ class InitiateBackupService if ($period > 0) { $previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, $period); if ($previous->count() >= $limit) { - throw new TooManyRequestsHttpException(CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addSeconds($period)), sprintf('Only %d backups may be generated within a %d second span of time.', $limit, $period)); + $message = sprintf('Only %d backups may be generated within a %d second span of time.', $limit, $period); + + throw new TooManyRequestsHttpException(CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addSeconds($period)), $message); } } - // Check if the server has reached or exceeded it's backup limit - if (!$server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) { + // Check if the server has reached or exceeded it's backup limit. + $successful = $server->backups()->where('is_successful', true); + if (!$server->backup_limit || $successful->count() >= $server->backup_limit) { // Do not allow the user to continue if this server is already at its limit and can't override. if (!$override || $server->backup_limit <= 0) { throw new TooManyBackupsException($server->backup_limit); } - // Get the oldest backup the server has. - /** @var \Pterodactyl\Models\Backup $oldestBackup */ - $oldestBackup = $server->backups()->where('is_successful', true)->orderBy('created_at')->first(); + // Get the oldest backup the server has that is not "locked" (indicating a backup that should + // never be automatically purged). If we find a backup we will delete it and then continue with + // this process. If no backup is found that can be used an exception is thrown. + /** @var \Pterodactyl\Models\Backup $oldest */ + $oldest = $successful->where('is_locked', false)->orderBy('created_at')->first(); + if (!$oldest) { + throw new TooManyBackupsException($server->backup_limit); + } - // Delete the oldest backup. - $this->deleteBackupService->handle($oldestBackup); + $this->deleteBackupService->handle($oldest); } return $this->connection->transaction(function () use ($server, $name) { @@ -131,6 +160,7 @@ class InitiateBackupService 'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()), 'ignored_files' => array_values($this->ignoredFiles ?? []), 'disk' => $this->backupManager->getDefaultAdapter(), + 'is_locked' => $this->isLocked, ], true, true); $this->daemonBackupRepository->setServer($server) diff --git a/app/Transformers/Api/Client/BackupTransformer.php b/app/Transformers/Api/Client/BackupTransformer.php index fe341ff16..ae6ae5213 100644 --- a/app/Transformers/Api/Client/BackupTransformer.php +++ b/app/Transformers/Api/Client/BackupTransformer.php @@ -19,6 +19,7 @@ class BackupTransformer extends BaseClientTransformer return [ 'uuid' => $backup->uuid, 'is_successful' => $backup->is_successful, + 'is_locked' => $backup->is_locked, 'name' => $backup->name, 'ignored_files' => $backup->ignored_files, 'checksum' => $backup->checksum, diff --git a/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php b/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php new file mode 100644 index 000000000..28826822f --- /dev/null +++ b/database/migrations/2021_05_03_201016_add_support_for_locking_a_backup.php @@ -0,0 +1,32 @@ +unsignedTinyInteger('is_locked')->after('is_successful')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('backups', function (Blueprint $table) { + $table->dropColumn('is_locked'); + }); + } +} diff --git a/resources/scripts/api/server/backups/createServerBackup.ts b/resources/scripts/api/server/backups/createServerBackup.ts index a27d5d146..3167b5b46 100644 --- a/resources/scripts/api/server/backups/createServerBackup.ts +++ b/resources/scripts/api/server/backups/createServerBackup.ts @@ -2,12 +2,18 @@ import http from '@/api/http'; import { ServerBackup } from '@/api/server/types'; import { rawDataToServerBackup } from '@/api/transformers'; -export default (uuid: string, name?: string, ignored?: string): Promise => { - return new Promise((resolve, reject) => { - http.post(`/api/client/servers/${uuid}/backups`, { - name, ignored, - }) - .then(({ data }) => resolve(rawDataToServerBackup(data))) - .catch(reject); +interface RequestParameters { + name?: string; + ignored?: string; + isLocked: boolean; +} + +export default async (uuid: string, params: RequestParameters): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/backups`, { + name: params.name, + ignored: params.ignored, + is_locked: params.isLocked, }); + + return rawDataToServerBackup(data); }; diff --git a/resources/scripts/api/server/types.d.ts b/resources/scripts/api/server/types.d.ts index dd871162c..7e3e540c0 100644 --- a/resources/scripts/api/server/types.d.ts +++ b/resources/scripts/api/server/types.d.ts @@ -3,6 +3,7 @@ export type ServerStatus = 'installing' | 'install_failed' | 'suspended' | 'rest export interface ServerBackup { uuid: string; isSuccessful: boolean; + isLocked: boolean; name: string; ignoredFiles: string; checksum: string; diff --git a/resources/scripts/api/transformers.ts b/resources/scripts/api/transformers.ts index 64db56f9a..069baf126 100644 --- a/resources/scripts/api/transformers.ts +++ b/resources/scripts/api/transformers.ts @@ -58,6 +58,7 @@ export const rawDataToFileObject = (data: FractalResponseData): FileObject => ({ export const rawDataToServerBackup = ({ attributes }: FractalResponseData): ServerBackup => ({ uuid: attributes.uuid, isSuccessful: attributes.is_successful, + isLocked: attributes.is_locked, name: attributes.name, ignoredFiles: attributes.ignored_files, checksum: attributes.checksum, diff --git a/resources/scripts/components/server/backups/BackupContextMenu.tsx b/resources/scripts/components/server/backups/BackupContextMenu.tsx index 06f101ba6..146ab50e3 100644 --- a/resources/scripts/components/server/backups/BackupContextMenu.tsx +++ b/resources/scripts/components/server/backups/BackupContextMenu.tsx @@ -1,10 +1,16 @@ import React, { useState } from 'react'; -import { faBoxOpen, faCloudDownloadAlt, faEllipsisH, faLock, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import { + faBoxOpen, + faCloudDownloadAlt, + faEllipsisH, + faLock, + faTrashAlt, + faUnlock, +} from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import DropdownMenu, { DropdownButtonRow } from '@/components/elements/DropdownMenu'; import getBackupDownloadUrl from '@/api/server/backups/getBackupDownloadUrl'; import useFlash from '@/plugins/useFlash'; -import ChecksumModal from '@/components/server/backups/ChecksumModal'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import deleteBackup from '@/api/server/backups/deleteBackup'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; @@ -15,6 +21,7 @@ import { ServerBackup } from '@/api/server/types'; import { ServerContext } from '@/state/server'; import Input from '@/components/elements/Input'; import { restoreServerBackup } from '@/api/server/backups'; +import http, { httpErrorToHuman } from '@/api/http'; interface Props { backup: ServerBackup; @@ -76,14 +83,35 @@ export default ({ backup }: Props) => { .then(() => setModal('')); }; + const onLockToggle = () => { + if (backup.isLocked && modal !== 'unlock') { + return setModal('unlock'); + } + + http.post(`/api/client/servers/${uuid}/backups/${backup.uuid}/lock`) + .then(() => mutate(data => ({ + ...data, + items: data.items.map(b => b.uuid !== backup.uuid ? b : { + ...b, + isLocked: !b.isLocked, + }), + }), false)) + .catch(error => alert(httpErrorToHuman(error))) + .then(() => setModal('')); + }; + return ( <> - setModal('')} - checksum={backup.checksum} - /> + setModal('')} + buttonText={'Yes, unlock'} + > + Are you sure you want to unlock this backup? It will no longer be protected from automated or + accidental deletions. + { Restore - setModal('checksum')}> - - Checksum - - setModal('delete')}> - - Delete - + <> + + + {backup.isLocked ? 'Unlock' : 'Lock'} + + {!backup.isLocked && + setModal('delete')}> + + Delete + + } +
diff --git a/resources/scripts/components/server/backups/BackupRow.tsx b/resources/scripts/components/server/backups/BackupRow.tsx index 6a3518537..570fd574d 100644 --- a/resources/scripts/components/server/backups/BackupRow.tsx +++ b/resources/scripts/components/server/backups/BackupRow.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faArchive, faEllipsisH } from '@fortawesome/free-solid-svg-icons'; +import { faArchive, faEllipsisH, faLock } from '@fortawesome/free-solid-svg-icons'; import { format, formatDistanceToNow } from 'date-fns'; import Spinner from '@/components/elements/Spinner'; import { bytesToHuman } from '@/helpers'; @@ -45,7 +45,10 @@ export default ({ backup, className }: Props) => {
{backup.completedAt ? - + backup.isLocked ? + + : + : } @@ -65,7 +68,7 @@ export default ({ backup, className }: Props) => { }

- {backup.uuid} + {backup.checksum}

diff --git a/resources/scripts/components/server/backups/ChecksumModal.tsx b/resources/scripts/components/server/backups/ChecksumModal.tsx deleted file mode 100644 index a0a318d67..000000000 --- a/resources/scripts/components/server/backups/ChecksumModal.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -import tw from 'twin.macro'; - -const ChecksumModal = ({ checksum, ...props }: RequiredModalProps & { checksum: string }) => ( - -

Verify file checksum

-

- The checksum of this file is: -

-
-            {checksum}
-        
-
-); - -export default ChecksumModal; diff --git a/resources/scripts/components/server/backups/CreateBackupButton.tsx b/resources/scripts/components/server/backups/CreateBackupButton.tsx index 754bc0245..c26dd728e 100644 --- a/resources/scripts/components/server/backups/CreateBackupButton.tsx +++ b/resources/scripts/components/server/backups/CreateBackupButton.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Field as FormikField, Form, Formik, FormikHelpers, useFormikContext } from 'formik'; -import { object, string } from 'yup'; +import { boolean, object, string } from 'yup'; import Field from '@/components/elements/Field'; import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper'; import useFlash from '@/plugins/useFlash'; @@ -12,10 +12,13 @@ import tw from 'twin.macro'; import { Textarea } from '@/components/elements/Input'; import getServerBackups from '@/api/swr/getServerBackups'; import { ServerContext } from '@/state/server'; +import FormikSwitch from '@/components/elements/FormikSwitch'; +import Can from '@/components/elements/Can'; interface Values { name: string; ignored: string; + isLocked: boolean; } const ModalContent = ({ ...props }: RequiredModalProps) => { @@ -26,14 +29,12 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {

Create server backup

-
- -
-
+ +
{
-
+ +
+ +
+
+
@@ -67,9 +77,9 @@ export default () => { clearFlashes('backups:create'); }, [ visible ]); - const submit = ({ name, ignored }: Values, { setSubmitting }: FormikHelpers) => { + const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('backups:create'); - createServerBackup(uuid, name, ignored) + createServerBackup(uuid, values) .then(backup => { mutate(data => ({ ...data, items: data.items.concat(backup) }), false); setVisible(false); @@ -85,10 +95,11 @@ export default () => { {visible && setVisible(false)}/> diff --git a/resources/scripts/hoc/asModal.tsx b/resources/scripts/hoc/asModal.tsx index 1e680fe33..452aa7eb1 100644 --- a/resources/scripts/hoc/asModal.tsx +++ b/resources/scripts/hoc/asModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Modal, { ModalProps } from '@/components/elements/Modal'; +import PortaledModal, { ModalProps } from '@/components/elements/Modal'; import ModalContext from '@/context/ModalContext'; export interface AsModalProps { @@ -57,7 +57,7 @@ function asModal

(modalProps?: ExtendedModalProps | ((props: P render () { return ( this.state.render ? - this.setState({ render: false }, () => { @@ -75,7 +75,7 @@ function asModal

(modalProps?: ExtendedModalProps | ((props: P > - + : null ); diff --git a/routes/api-client.php b/routes/api-client.php index 86dcff070..ec4988bc8 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -103,6 +103,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::post('/', 'Servers\BackupController@store'); Route::get('/{backup}', 'Servers\BackupController@view'); Route::get('/{backup}/download', 'Servers\BackupController@download'); + Route::post('/{backup}/lock', 'Servers\BackupController@toggleLock'); Route::post('/{backup}/restore', 'Servers\BackupController@restore'); Route::delete('/{backup}', 'Servers\BackupController@delete'); }); From 0cd1362b05c297e52846721aa615d4cdc4c0cbca Mon Sep 17 00:00:00 2001 From: aussieserverhosts <65438932+aussieserverhosts@users.noreply.github.com> Date: Sat, 8 May 2021 01:18:13 +1000 Subject: [PATCH 43/51] Added Aussie Server Hosts to sponsors list (#3336) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ace16213a..f19eba47a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. | | [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | | [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! | +| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | ## Documentation * [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html) From 5a82dd6a18a257505959fb8720bbec06124e8eb7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 8 May 2021 10:37:18 -0700 Subject: [PATCH 44/51] Update to TypeScript 4 --- .eslintignore | 2 + .eslintrc.yml | 7 + package.json | 8 +- .../components/server/WebsocketHandler.tsx | 24 +- .../server/files/FileEditContainer.tsx | 2 +- tsconfig.json | 16 +- webpack.config.js | 13 +- yarn.lock | 308 ++++++++++++++---- 8 files changed, 286 insertions(+), 94 deletions(-) diff --git a/.eslintignore b/.eslintignore index fff6b3500..6fdfb93fe 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,4 @@ public +node_modules resources/views +webpack.config.js diff --git a/.eslintrc.yml b/.eslintrc.yml index b18f90af9..001ba7f9a 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -76,6 +76,13 @@ rules: - 1 - "line-aligned" "react/jsx-closing-tag-location": 1 + # This setup is required to avoid a spam of errors when running eslint about React being + # used before it is defined. + # + # see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use + no-use-before-define: 0 + "@typescript-eslint/no-use-before-define": + - warn overrides: - files: - "**/*.tsx" diff --git a/package.json b/package.json index 38be36507..462b95054 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,8 @@ "@types/uuid": "^3.4.5", "@types/webpack-env": "^1.15.2", "@types/yup": "^0.29.3", - "@typescript-eslint/eslint-plugin": "^3.5.0", - "@typescript-eslint/parser": "^3.5.0", + "@typescript-eslint/eslint-plugin": "^4.22.1", + "@typescript-eslint/parser": "^4.22.1", "autoprefixer": "^10.1.0", "babel-loader": "^8.0.6", "babel-plugin-styled-components": "^1.12.0", @@ -90,7 +90,7 @@ "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^4.0.5", "eslint-plugin-standard": "^4.0.1", - "fork-ts-checker-webpack-plugin": "^5.0.6", + "fork-ts-checker-webpack-plugin": "^6.2.6", "postcss": "^8.2.1", "redux-devtools-extension": "^2.13.8", "source-map-loader": "^1.0.1", @@ -98,7 +98,7 @@ "svg-url-loader": "^6.0.0", "terser-webpack-plugin": "^3.0.6", "twin.macro": "^2.0.7", - "typescript": "^3.9.6", + "typescript": "^4.2.4", "webpack": "^4.43.0", "webpack-assets-manifest": "^3.1.1", "webpack-bundle-analyzer": "^3.8.0", diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 10b91dd47..724937f01 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -20,6 +20,18 @@ export default () => { const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); + const updateToken = (uuid: string, socket: Websocket) => { + if (updatingToken) return; + + updatingToken = true; + getWebsocketToken(uuid) + .then(data => socket.setToken(data.token, true)) + .catch(error => console.error(error)) + .then(() => { + updatingToken = false; + }); + }; + const connect = (uuid: string) => { const socket = new Websocket(); @@ -73,18 +85,6 @@ export default () => { .catch(error => console.error(error)); }; - const updateToken = (uuid: string, socket: Websocket) => { - if (updatingToken) return; - - updatingToken = true; - getWebsocketToken(uuid) - .then(data => socket.setToken(data.token, true)) - .catch(error => console.error(error)) - .then(() => { - updatingToken = false; - }); - }; - useEffect(() => { connected && setError(''); }, [ connected ]); diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx index 012875ab1..8a7feb9a3 100644 --- a/resources/scripts/components/server/files/FileEditContainer.tsx +++ b/resources/scripts/components/server/files/FileEditContainer.tsx @@ -24,7 +24,7 @@ const LazyCodemirrorEditor = lazy(() => import(/* webpackChunkName: "editor" */' export default () => { const [ error, setError ] = useState(''); - const { action } = useParams(); + const { action } = useParams<{ action: 'new' | string }>(); const [ loading, setLoading ] = useState(action === 'edit'); const [ content, setContent ] = useState(''); const [ modalVisible, setModalVisible ] = useState(false); diff --git a/tsconfig.json b/tsconfig.json index 4b14bec71..26423ecd6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,18 +3,20 @@ "target": "es2015", "module": "es2020", "jsx": "react", - "strict": true, - "noEmit": true, - "noImplicitReturns": true, "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "sourceMap": true, - "baseUrl": ".", "lib": [ "es2015", "dom" ], + "strict": true, + "noEmit": true, + "sourceMap": true, + "noImplicitReturns": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "baseUrl": ".", "importsNotUsedAsValues": "preserve", "paths": { "@/*": [ diff --git a/webpack.config.js b/webpack.config.js index e1cd0c951..c97abdd19 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -71,10 +71,17 @@ module.exports = { }, plugins: [ new AssetsManifestPlugin({ writeToDisk: true, publicPath: true, integrity: true, integrityHashes: ['sha384'] }), - new ForkTsCheckerWebpackPlugin(isProduction ? {} : { - eslint: { - files: `${path.join(__dirname, '/resources/scripts')}/**/*.{ts,tsx}`, + new ForkTsCheckerWebpackPlugin({ + typescript: { + mode: 'write-references', + diagnosticOptions: { + semantic: true, + syntactic: true, + }, }, + eslint: isProduction ? undefined : { + files: `${path.join(__dirname, '/resources/scripts')}/**/*.{ts,tsx}`, + } }), process.env.ANALYZE_BUNDLE ? new BundleAnalyzerPlugin({ analyzerHost: '0.0.0.0', diff --git a/yarn.lock b/yarn.lock index e59b38f21..6e8bb1438 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1103,6 +1103,27 @@ dependencies: purgecss "^3.1.3" +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== + dependencies: + "@nodelib/fs.stat" "2.0.4" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + dependencies: + "@nodelib/fs.scandir" "2.1.4" + fastq "^1.6.0" + "@npmcli/move-file@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" @@ -1138,10 +1159,6 @@ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw== -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - "@types/estree@*": version "0.0.45" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" @@ -1179,6 +1196,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== +"@types/json-schema@^7.0.5": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1311,65 +1333,75 @@ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.3.tgz#5a85024796bffe0eb01601bfc180fe218356dba4" integrity sha512-XxZFKnxzTfm+DR8MMBA35UUXfUPmjPpi8HJ90VZg7q/LIbtiOhVGJ26gNnATcflcpnIyf2Qm9A+oEhswaqoDpA== -"@typescript-eslint/eslint-plugin@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.5.0.tgz#e7736e0808b5fb947a5f9dd949ae6736a7226b84" - integrity sha512-m4erZ8AkSjoIUOf8s4k2V1xdL2c1Vy0D3dN6/jC9d7+nEqjY3gxXCkgi3gW/GAxPaA4hV8biaCoTVdQmfAeTCQ== +"@typescript-eslint/eslint-plugin@^4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.1.tgz#6bcdbaa4548553ab861b4e5f34936ead1349a543" + integrity sha512-kVTAghWDDhsvQ602tHBc6WmQkdaYbkcTwZu+7l24jtJiYvm9l+/y/b2BZANEezxPDiX5MK2ZecE+9BFi/YJryw== dependencies: - "@typescript-eslint/experimental-utils" "3.5.0" + "@typescript-eslint/experimental-utils" "4.22.1" + "@typescript-eslint/scope-manager" "4.22.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" + lodash "^4.17.15" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.5.0.tgz#d09f9ffb890d1b15a7ffa9975fae92eee05597c4" - integrity sha512-zGNOrVi5Wz0jcjUnFZ6QUD0MCox5hBuVwemGCew2qJzUX5xPoyR+0EzS5qD5qQXL/vnQ8Eu+nv03tpeFRwLrDg== +"@typescript-eslint/experimental-utils@4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.1.tgz#3938a5c89b27dc9a39b5de63a62ab1623ab27497" + integrity sha512-svYlHecSMCQGDO2qN1v477ax/IDQwWhc7PRBiwAdAMJE7GXk5stF4Z9R/8wbRkuX/5e9dHqbIWxjeOjckK3wLQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/types" "3.5.0" - "@typescript-eslint/typescript-estree" "3.5.0" + "@typescript-eslint/scope-manager" "4.22.1" + "@typescript-eslint/types" "4.22.1" + "@typescript-eslint/typescript-estree" "4.22.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.5.0.tgz#9ff8c11877c48df24e10e19d7bf542ee0359500d" - integrity sha512-sU07VbYB70WZHtgOjH/qfAp1+OwaWgrvD1Km1VXqRpcVxt971PMTU7gJtlrCje0M+Sdz7xKAbtiyIu+Y6QdnVA== +"@typescript-eslint/parser@^4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.22.1.tgz#a95bda0fd01d994a15fc3e99dc984294f25c19cc" + integrity sha512-l+sUJFInWhuMxA6rtirzjooh8cM/AATAe3amvIkqKFeMzkn85V+eLzb1RyuXkHak4dLfYzOmF6DXPyflJvjQnw== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.5.0" - "@typescript-eslint/types" "3.5.0" - "@typescript-eslint/typescript-estree" "3.5.0" - eslint-visitor-keys "^1.1.0" - -"@typescript-eslint/types@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.5.0.tgz#4e3d2a2272268d8ec3e3e4a37152a64956682639" - integrity sha512-Dreqb5idi66VVs1QkbAwVeDmdJG+sDtofJtKwKCZXIaBsINuCN7Jv5eDIHrS0hFMMiOvPH9UuOs4splW0iZe4Q== - -"@typescript-eslint/typescript-estree@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.5.0.tgz#dfc895db21a381b84f24c2a719f5bf9c600dcfdc" - integrity sha512-Na71ezI6QP5WVR4EHxwcBJgYiD+Sre9BZO5iJK2QhrmRPo/42+b0no/HZIrdD1sjghzlYv7t+7Jis05M1uMxQg== - dependencies: - "@typescript-eslint/types" "3.5.0" - "@typescript-eslint/visitor-keys" "3.5.0" + "@typescript-eslint/scope-manager" "4.22.1" + "@typescript-eslint/types" "4.22.1" + "@typescript-eslint/typescript-estree" "4.22.1" debug "^4.1.1" - glob "^7.1.6" + +"@typescript-eslint/scope-manager@4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.22.1.tgz#5bb357f94f9cd8b94e6be43dd637eb73b8f355b4" + integrity sha512-d5bAiPBiessSmNi8Amq/RuLslvcumxLmyhf1/Xa9IuaoFJ0YtshlJKxhlbY7l2JdEk3wS0EnmnfeJWSvADOe0g== + dependencies: + "@typescript-eslint/types" "4.22.1" + "@typescript-eslint/visitor-keys" "4.22.1" + +"@typescript-eslint/types@4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.1.tgz#bf99c6cec0b4a23d53a61894816927f2adad856a" + integrity sha512-2HTkbkdAeI3OOcWbqA8hWf/7z9c6gkmnWNGz0dKSLYLWywUlkOAQ2XcjhlKLj5xBFDf8FgAOF5aQbnLRvgNbCw== + +"@typescript-eslint/typescript-estree@4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.1.tgz#dca379eead8cdfd4edc04805e83af6d148c164f9" + integrity sha512-p3We0pAPacT+onSGM+sPR+M9CblVqdA9F1JEdIqRVlxK5Qth4ochXQgIyb9daBomyQKAXbygxp1aXQRV0GC79A== + dependencies: + "@typescript-eslint/types" "4.22.1" + "@typescript-eslint/visitor-keys" "4.22.1" + debug "^4.1.1" + globby "^11.0.1" is-glob "^4.0.1" - lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.5.0.tgz#73c1ea2582f814735e4afdc1cf6f5e3af78db60a" - integrity sha512-7cTp9rcX2sz9Z+zua9MCOX4cqp5rYyFD5o8LlbSpXrMTXoRdngTtotRZEkm8+FNMHPWYFhitFK+qt/brK8BVJQ== +"@typescript-eslint/visitor-keys@4.22.1": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.1.tgz#6045ae25a11662c671f90b3a403d682dfca0b7a6" + integrity sha512-WPkOrIRm+WCLZxXQHCi+WG8T2MMTUFR70rWjdWYddLT7cEfb2P4a3O/J2U1FBVsSFTocXLCoXWY6MZGejeStvQ== dependencies: - eslint-visitor-keys "^1.1.0" + "@typescript-eslint/types" "4.22.1" + eslint-visitor-keys "^2.0.0" "@webassemblyjs/ast@1.9.0": version "1.9.0" @@ -1719,6 +1751,11 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -1967,7 +2004,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" dependencies: @@ -2245,6 +2282,21 @@ chokidar@^3.4.0: optionalDependencies: fsevents "~2.1.2" +chokidar@^3.4.2: + version "3.5.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" + integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.3.1" + chownr@^1.0.1, chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -2822,6 +2874,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -3179,6 +3238,11 @@ eslint-visitor-keys@^1.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + eslint@^7.4.0: version "7.4.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f" @@ -3385,6 +3449,18 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.1.1: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -3393,6 +3469,13 @@ fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fastq@^1.6.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" + integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -3546,20 +3629,23 @@ foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" -fork-ts-checker-webpack-plugin@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.6.tgz#02af713af02e47b338a3992279209bc65d34c773" - integrity sha512-8h4S7WANr69Resw+tMd5U23xdbVPsT+VOeMKQhUaKhkGeMm0fNf7ObJPP81WG+tsmy4LK4ayIf0JXBY+hy6vMw== +fork-ts-checker-webpack-plugin@^6.2.6: + version "6.2.6" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.2.6.tgz#cd105c9064d05ad9b518fc3cc9906389daa1a7ec" + integrity sha512-f/oF2BFFPKEWQ3wgfq4bWALSDm7+f21shVONplo1xHKs1IdMfdmDa/aREgEurkIyrsyMFed42W7NVp4mh4DXzg== dependencies: "@babel/code-frame" "^7.8.3" - chalk "^2.4.1" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" cosmiconfig "^6.0.0" deepmerge "^4.2.2" fs-extra "^9.0.0" + glob "^7.1.6" memfs "^3.1.2" minimatch "^3.0.4" - schema-utils "1.0.0" - semver "^5.6.0" + schema-utils "2.7.0" + semver "^7.3.2" tapable "^1.0.0" formik@^2.2.6: @@ -3654,6 +3740,11 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3708,7 +3799,14 @@ glob-parent@^5.0.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -glob@^7.0.0, glob@^7.1.6: +glob-parent@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.0: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -3731,6 +3829,18 @@ glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -3782,6 +3892,18 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globby@^11.0.1: + version "11.0.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -4082,6 +4204,11 @@ ignore@^5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558" +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + immer@7.0.9: version "7.0.9" resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e" @@ -4717,6 +4844,11 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4739,6 +4871,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -5475,6 +5615,11 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" + integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -5768,6 +5913,11 @@ querystringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" @@ -5982,6 +6132,13 @@ readdirp@~3.4.0: dependencies: picomatch "^2.2.1" +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + reaptcha@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/reaptcha/-/reaptcha-1.7.2.tgz#d829f54270c241f46501e92a5a7badeb1fcf372d" @@ -6184,6 +6341,11 @@ retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -6210,6 +6372,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -6246,7 +6415,16 @@ scheduler@^0.17.0: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@1.0.0, schema-utils@^1.0.0: +schema-utils@2.7.0, schema-utils@^2.6.5, schema-utils@^2.6.6: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" dependencies: @@ -6261,15 +6439,6 @@ schema-utils@^2.6.0: ajv "^6.10.2" ajv-keywords "^3.4.1" -schema-utils@^2.6.5, schema-utils@^2.6.6: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -6436,6 +6605,11 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -7126,10 +7300,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^3.9.6: - version "3.9.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" - integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== +typescript@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" From 76ac1998cfbb8798ce69844fccc196673b5294af Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sun, 16 May 2021 12:47:36 -0400 Subject: [PATCH 45/51] Don't allow backups to be made via schedules if limit = 0 (#3323) --- .../Client/Servers/ScheduleTaskController.php | 10 ++++++ .../components/dashboard/ApiKeyModal.tsx | 3 +- .../server/databases/DatabasesContainer.tsx | 2 +- .../schedules/ScheduleEditContainer.tsx | 2 +- .../server/schedules/TaskDetailsModal.tsx | 34 +++++++++++-------- .../CreateServerScheduleTaskTest.php | 12 ++++--- 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 12bcf968c..4ceed6550 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -39,6 +39,7 @@ class ScheduleTaskController extends ClientApiController * * @return array * + * @throws \Pterodactyl\Exceptions\Model\HttpForbiddenException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\ServiceLimitExceededException */ @@ -49,6 +50,10 @@ class ScheduleTaskController extends ClientApiController throw new ServiceLimitExceededException("Schedules may not have more than {$limit} tasks associated with them. Creating this task would put this schedule over the limit."); } + if ($server->backup_limit === 0 && $request->action === 'backup') { + throw new HttpForbiddenException("A backup task cannot be created when the server's backup limit is set to 0."); + } + /** @var \Pterodactyl\Models\Task|null $lastTask */ $lastTask = $schedule->tasks()->orderByDesc('sequence_id')->first(); @@ -72,6 +77,7 @@ class ScheduleTaskController extends ClientApiController * * @return array * + * @throws \Pterodactyl\Exceptions\Model\HttpForbiddenException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ @@ -81,6 +87,10 @@ class ScheduleTaskController extends ClientApiController throw new NotFoundHttpException(); } + if ($server->backup_limit === 0 && $request->action === 'backup') { + throw new HttpForbiddenException("A backup task cannot be created when the server's backup limit is set to 0."); + } + $this->repository->update($task->id, [ 'action' => $request->input('action'), 'payload' => $request->input('payload') ?? '', diff --git a/resources/scripts/components/dashboard/ApiKeyModal.tsx b/resources/scripts/components/dashboard/ApiKeyModal.tsx index ba6568b84..e2c129a51 100644 --- a/resources/scripts/components/dashboard/ApiKeyModal.tsx +++ b/resources/scripts/components/dashboard/ApiKeyModal.tsx @@ -3,6 +3,7 @@ import tw from 'twin.macro'; import Button from '@/components/elements/Button'; import asModal from '@/hoc/asModal'; import ModalContext from '@/context/ModalContext'; +import CopyOnClick from '@/components/elements/CopyOnClick'; interface Props { apiKey: string; @@ -19,7 +20,7 @@ const ApiKeyModal = ({ apiKey }: Props) => { shown again.

-                {apiKey}
+                {apiKey}